Skip to content

Conversation

saqadri
Copy link
Collaborator

@saqadri saqadri commented Oct 8, 2025

Ensure OpenTelemetrySettings.exporters tolerate list items that drop the type, inferring it when possible and emitting a clear error otherwise.

Example:
base config (mcp_agent.config.yaml):

        otel:
          exporters:
            - type: otlp
              endpoint: "https://collector.default/v1/traces"

overlay (mcp_agent.secrets.yaml):

        otel:
          exporters:
            - headers:
                Authorization: !developer_secret OTEL_AUTH

merged result (what we load):

        otel:
          exporters:
            - type: otlp
              endpoint: "https://collector.default/v1/traces"
              headers:
                Authorization: !developer_secret OTEL_AUTH
```

also mixed entries like `["console", {"type": "file"}]` are handled fine:

During the before validator we normalize the list. Strings stay as strings; dict entries keep or infer their type.
In the `after` validator we walk the list: literal strings become ConsoleExporterSettings, and dict entries model-validate into their typed counterparts.

So a config like:
```
  otel:
    exporters:
      - console
      - { type: file, path: "traces.jsonl" }
```
will end up as the expected mix of typed exporters at runtime.

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

## Summary by CodeRabbit

* New Features
  * Telemetry exporter configuration is now more flexible: the exporter type can be inferred automatically when omitted.
  * Improved backward compatibility: legacy exporter fields are accepted and converted into the new typed format.
  * Clearer validation: helpful errors are shown when the exporter type cannot be determined.

* Tests
  * Added comprehensive tests covering type inference for OTLP and File exporters, preservation of provided settings, and error handling for unrecognized configurations.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

Copy link

coderabbitai bot commented Oct 8, 2025

Walkthrough

Adds type inference for OpenTelemetry exporter configs lacking explicit "type". Introduces OpenTelemetrySettings._guess_exporter_type and updates _coerce_exporters_schema to normalize list-based exporters, infer types (otlp/file) from keys, and raise errors when inference fails. Maintains legacy string and per-exporter field handling. Adds tests covering inference and error cases.

Changes

Cohort / File(s) Summary
Config logic
src/mcp_agent/config.py
Added _guess_exporter_type to infer exporter type from keys; enhanced _coerce_exporters_schema to normalize list exporters, coerce BaseModel to dicts, infer/assign missing types, and preserve legacy string/field handling; added validation error on ambiguous entries.
Tests
tests/test_config_exporters.py
New tests for type inference: OTLP from endpoint/headers or headers-only; File from path/path_settings; error on unrecognized fields without type. Validates preservation of inferred fields.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant Config as OpenTelemetrySettings
  participant Coerce as _coerce_exporters_schema
  participant Guess as _guess_exporter_type

  User->>Config: load(settings with exporters)
  Config->>Coerce: normalize exporters
  alt exporters is list
    loop each entry
      Coerce->>Coerce: if BaseModel -> to_dict
      alt entry has explicit type
        Coerce->>Coerce: validate and keep
      else entry missing type
        Coerce->>Guess: infer type by keys
        alt type inferred (otlp/file)
          Coerce->>Coerce: inject inferred type, keep fields
        else cannot infer
          Coerce-->>User: raise ValueError
        end
      end
    end
  else exporters is string (console/file/otlp)
    Coerce->>Coerce: up-convert using legacy fields
  end
  Coerce-->>Config: normalized typed exporters
  Config-->>User: finalized settings
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

I twitch my whiskers at configs untold,
From keys I sniff what types they hold.
OTLP scents? File crumbs on the trail—
I hop through lists where hints prevail.
When clues go missing, I thump: “Nope!”
Then nibble tests—crisp leaves of scope.
(\_/) ✅ Inference: nope to dope!

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title directly references the newly introduced fuzzy matching behavior applied to the exporter schema, which aligns with the core change of inferring missing exporter types in overlay configurations. Although the “[rfc]” prefix is not strictly necessary, the title remains focused on the main feature.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/fuzzy_exporters

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@saqadri saqadri requested a review from alienzach October 8, 2025 20:04
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (3)
src/mcp_agent/config.py (2)

608-624: Consider field-overlap edge cases and type precision.

The inference logic checks OTLP keys before file keys, so an entry with both endpoint and path would be inferred as OTLP, potentially ignoring the path field. While this may not be a current issue, it could cause confusion if configurations inadvertently include mixed keys.

Additionally, the return type str | None is less precise than Literal["otlp", "file"] | None, which would provide better type safety.

Consider these improvements:

  1. Add validation to reject entries with conflicting keys (e.g., both OTLP and file keys present).
  2. Refine the return type annotation:
-def _guess_exporter_type(entry: Dict[str, Any]) -> str | None:
+def _guess_exporter_type(entry: Dict[str, Any]) -> Literal["otlp", "file"] | None:
  1. Document that console exporters cannot be inferred and must have an explicit type field.

639-666: LGTM with minor suggestion!

The normalization and type inference logic is well-implemented. It correctly handles BaseModel conversion, infers missing types, and provides clear error messages when inference fails.

One minor enhancement: The error message references "secrets overlays," which is helpful context from the PR description. However, since this validator runs for all configurations (not just overlays), consider making the message more general:

                        else:
                            raise ValueError(
                                "OpenTelemetry exporter entries must include a 'type'. "
                                "Unable to infer exporter type from fields "
                                f"{sorted(entry_dict.keys())}. "
-                               "Ensure each exporter in secrets overlays sets `type`."
+                               "Provide an explicit 'type' field or include recognizable "
+                               "keys (e.g., 'endpoint' for OTLP, 'path' for file)."
                            )
tests/test_config_exporters.py (1)

106-148: Excellent test coverage!

The new tests comprehensively validate the type inference logic across the key scenarios:

  • OTLP inference from endpoint and headers (both together and headers alone)
  • File inference from path_settings
  • Error handling for unrecognizable fields

The tests are well-structured, use existing assertion helpers, and align with the PR objectives of enabling fuzzy matching for exporter configurations.

Consider adding a test case that explicitly verifies console exporters require an explicit type field, since they cannot be inferred:

def test_console_type_cannot_be_inferred():
    """Console exporters require explicit type since they have no unique keys."""
    # This should fail since console has no unique keys for inference
    with pytest.raises(ValueError, match="must include a 'type'"):
        OpenTelemetrySettings(exporters=[{}])

This would document the intended behavior and prevent regression if the inference logic changes.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1c8900e and 5e74f06.

📒 Files selected for processing (2)
  • src/mcp_agent/config.py (2 hunks)
  • tests/test_config_exporters.py (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
tests/test_config_exporters.py (1)
src/mcp_agent/config.py (2)
  • OpenTelemetrySettings (576-767)
  • OTLPExporterSettings (560-563)

Comment on lines 550 to 563
class ConsoleExporterSettings(OpenTelemetryExporterBase):
type: Literal["console"] = "console"


class FileExporterSettings(OpenTelemetryExporterBase):
type: Literal["file"] = "file"
path: str | None = None
path_settings: TracePathSettings | None = None


class OTLPExporterSettings(OpenTelemetryExporterBase):
type: Literal["otlp"] = "otlp"
endpoint: str | None = None
headers: Dict[str, str] | None = None
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think the root cause of issue is here. when we have "type: console", for example:

  1. because "console" is the default value of type, when we deserialize settings object from base setting yaml with "ignore defaults", this ConsoleExporterSettings will be deserialzied to empty object {}.
  2. then, we will use the yaml merge to merge the secrets volume into this base settings object, Pydantic validator will get an {} empty and Pydantic will fail at validation because without "type", it cannot decide which type that is.

@saqadri
Copy link
Collaborator Author

saqadri commented Oct 9, 2025

Closing this PR in favor of #551

@saqadri saqadri closed this Oct 9, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants