Skip to content

Add LexMexTool - Mexican federal law legal assistant - #6971

Open
Volpsmx wants to merge 5 commits into
crewAIInc:mainfrom
Volpsmx:main
Open

Add LexMexTool - Mexican federal law legal assistant#6971
Volpsmx wants to merge 5 commits into
crewAIInc:mainfrom
Volpsmx:main

Conversation

@Volpsmx

@Volpsmx Volpsmx commented Aug 12, 2026

Copy link
Copy Markdown

Description

Adds LexMexTool, a tool for querying LEX-MEX — a Spanish-language
legal assistant covering all 316 Mexican federal laws, kept in sync
with diputados.gob.mx. Answers are returned with the exact law and
article cited (no hallucinated sources).

What's included

  • crewai_tools/tools/lexmex_tool/lexmex_tool.py — the tool implementation
  • crewai_tools/tools/lexmex_tool/README.md — usage docs and required env vars
  • crewai_tools/tools/lexmex_tool/__init__.py — module export
  • tests/tools/test_lexmex_tool.py — unit tests (API key resolution + happy path, no real network calls)
  • Registered LexMexTool in crewai_tools/__init__.py (import + __all__)

Requirements

Requires a LEX-MEX API key (LEXMEX_API_KEY env var or api_key=
param), obtained by signing up at https://lex-mex.xyz.

Checklist (per BUILDING_TOOLS.md)

  • Subclasses BaseTool with args_schema and explicit field descriptions
  • _run(...) implemented, lazy API key resolution with clear error messages
  • Tests added under tests/tools/, no real network calls (mocked)
  • README with usage and required env vars
  • uv run pytest and pre-commit run -a run locally — not run in this environment; happy to fix any CI failures a maintainer flags

Add tests for LexMexTool to ensure API key handling and functionality.
This file implements the LexMexTool for querying Mexican federal laws using the LEX-MEX API. It includes input validation, error handling, and response formatting.
Added README for LexMexTool with installation, usage, and common errors.
@coderabbitai

coderabbitai Bot commented Aug 12, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Changes

LexMexTool

Layer / File(s) Summary
Implement LexMexTool API flow
lib/crewai-tools/src/crewai_tools/tools/lexmex_tool/lexmex_tool.py, lib/crewai-tools/src/lib/crewai-tools/tests/tools/test_lexmex_tool.py, lib/crewai-tools/src/crewai_tools/tools/lexmex_tool/README.md
Adds Spanish-question validation, API-key resolution, authenticated LEX-MEX requests, HTTP error handling, response formatting, documentation, and tests.
Expose LexMexTool publicly
lib/crewai-tools/src/crewai_tools/tools/lexmex_tool/__init__.py, lib/crewai-tools/src/crewai_tools/__init__.py
Exports LexMexTool from its tool package and the root crewai_tools package.

Sequence Diagram(s)

sequenceDiagram
  participant CrewAI
  participant LexMexTool
  participant LEXMEX_API
  CrewAI->>LexMexTool: Submit pregunta
  LexMexTool->>LEXMEX_API: Send authenticated legal query
  LEXMEX_API-->>LexMexTool: Return answer, citations, and confidence
  LexMexTool-->>CrewAI: Return formatted response
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the main change: adding LexMexTool for Mexican federal law assistance.
Description check ✅ Passed The description accurately explains LexMexTool, its implementation, documentation, tests, configuration, and validation status.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

Warning

⚠️ This pull request has been flagged as potential spam (promotional) by CodeRabbit slop detection and should be reviewed carefully.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@lib/crewai-tools/src/crewai_tools/tools/lexmex_tool/lexmex_tool.py`:
- Around line 80-98: Prevent caller-controlled hosts in LexMexTool._run by
removing api_base from the request URL and always using the fixed
LEXMEX_API_BASE constant. Ensure the X-API-Key resolved by _resolved_key is sent
only to the trusted LEX-MEX endpoint.
- Line 79: Update the api_key field in the Lexmex tool model to use Field with
default=None, exclude=True, and repr=False, ensuring supplied credentials are
omitted from model_dump serialization and representations.

In `@lib/crewai-tools/src/lib/crewai-tools/tests/tools/test_lexmex_tool.py`:
- Around line 13-36: Update test_happy_path to capture the mocked requests.post
invocation and assert the required LEX-MEX request contract: endpoint, request
body, API-key header, and timeout. Keep the existing response and output
assertions, using the mock call arguments to verify the successful query sends
the expected values.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: e2ca9030-c62b-418d-b5f0-0b93c731ff9d

📥 Commits

Reviewing files that changed from the base of the PR and between 28d868c and 76c77ef.

📒 Files selected for processing (5)
  • lib/crewai-tools/src/crewai_tools/__init__.py
  • lib/crewai-tools/src/crewai_tools/tools/lexmex_tool/README.md
  • lib/crewai-tools/src/crewai_tools/tools/lexmex_tool/__init__.py
  • lib/crewai-tools/src/crewai_tools/tools/lexmex_tool/lexmex_tool.py
  • lib/crewai-tools/src/lib/crewai-tools/tests/tools/test_lexmex_tool.py

)
args_schema: Type[BaseModel] = LexMexInput

api_key: Optional[str] = None

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the BaseTool serialization path and the LexMexTool credential field.
ast-grep outline lib/crewai/src/crewai/tools/base_tool.py --items all --match BaseTool
rg -n -C 4 'model_dump\(mode="json"\)|plain_serializer' \
  lib/crewai/src/crewai/tools/base_tool.py
rg -n -C 2 '\bapi_key\b' \
  lib/crewai-tools/src/crewai_tools/tools/lexmex_tool/lexmex_tool.py

Repository: crewAIInc/crewAI

Length of output: 2203


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the exact serialization behavior and the complete credential usage path.
sed -n '100,155p' lib/crewai/src/crewai/tools/base_tool.py
sed -n '155,250p' lib/crewai/src/crewai/tools/base_tool.py
sed -n '65,140p' lib/crewai-tools/src/crewai_tools/tools/lexmex_tool/lexmex_tool.py

# Identify the Pydantic dependency and run a standalone serialization probe when available.
rg -n -C 2 'pydantic' pyproject.toml lib/crewai/pyproject.toml lib/crewai-tools/pyproject.toml 2>/dev/null || true
python3 - <<'PY'
try:
    import pydantic
    from pydantic import BaseModel, Field
    from typing import Optional

    class Probe(BaseModel):
        api_key: Optional[str] = Field(default=None, exclude=True, repr=False)
        api_base: str = "https://example.invalid"

    class PlainProbe(BaseModel):
        api_key: Optional[str] = None
        api_base: str = "https://example.invalid"

    print("pydantic_version=", pydantic.__version__)
    print("excluded_dump=", Probe(api_key="secret").model_dump(mode="json"))
    print("excluded_dump_none=", Probe().model_dump(mode="json"))
    print("plain_dump=", PlainProbe(api_key="secret").model_dump(mode="json"))
except Exception as exc:
    print(type(exc).__name__, str(exc))
PY

Repository: crewAIInc/crewAI

Length of output: 10013


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '100,155p' lib/crewai/src/crewai/tools/base_tool.py
sed -n '155,250p' lib/crewai/src/crewai/tools/base_tool.py
sed -n '65,140p' lib/crewai-tools/src/crewai_tools/tools/lexmex_tool/lexmex_tool.py

rg -n -C 2 'pydantic' pyproject.toml lib/crewai/pyproject.toml lib/crewai-tools/pyproject.toml 2>/dev/null || true

python3 - <<'PY'
try:
    import pydantic
    from pydantic import BaseModel, Field
    from typing import Optional

    class Probe(BaseModel):
        api_key: Optional[str] = Field(default=None, exclude=True, repr=False)
        api_base: str = "https://example.invalid"

    class PlainProbe(BaseModel):
        api_key: Optional[str] = None
        api_base: str = "https://example.invalid"

    print("pydantic_version=", pydantic.__version__)
    print("excluded_dump=", Probe(api_key="secret").model_dump(mode="json"))
    print("excluded_dump_none=", Probe().model_dump(mode="json"))
    print("plain_dump=", PlainProbe(api_key="secret").model_dump(mode="json"))
except Exception as exc:
    print(type(exc).__name__, str(exc))
PY

Repository: crewAIInc/crewAI

Length of output: 10013


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Trace where BaseTool values are serialized and whether Agent/Task configuration
# serializes tools through the BaseTool schema.
rg -n -C 4 'model_dump\(|tools:\s|list\[BaseTool\]|Sequence\[BaseTool\]|BaseTool' \
  lib/crewai/src/crewai | head -n 240

# Check whether LexMexTool already imports Field and inspect its class declaration.
sed -n '1,90p' lib/crewai-tools/src/crewai_tools/tools/lexmex_tool/lexmex_tool.py
rg -n -C 3 'class LexMexTool|Field\(' \
  lib/crewai-tools/src/crewai_tools/tools/lexmex_tool/lexmex_tool.py

Repository: crewAIInc/crewAI

Length of output: 22618


🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n -C 4 'model_dump\(|tools:\s|list\[BaseTool\]|Sequence\[BaseTool\]|BaseTool' \
  lib/crewai/src/crewai | head -n 240

sed -n '1,90p' lib/crewai-tools/src/crewai_tools/tools/lexmex_tool/lexmex_tool.py
rg -n -C 3 'class LexMexTool|Field\(' \
  lib/crewai-tools/src/crewai_tools/tools/lexmex_tool/lexmex_tool.py

Repository: crewAIInc/crewAI

Length of output: 27057


Exclude api_key from serialized tool state.

CrewAI serialization calls model_dump(mode="json"), and the current field includes supplied credentials. Define it as Field(default=None, exclude=True, repr=False) to prevent credential persistence.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/crewai-tools/src/crewai_tools/tools/lexmex_tool/lexmex_tool.py` at line
79, Update the api_key field in the Lexmex tool model to use Field with
default=None, exclude=True, and repr=False, ensuring supplied credentials are
omitted from model_dump serialization and representations.

Comment on lines +80 to +98
api_base: str = LEXMEX_API_BASE
timeout: int = 30

def _resolved_key(self) -> str:
key = self.api_key or os.getenv("LEXMEX_API_KEY")
if not key:
raise ValueError(
"Falta la API key de LEX-MEX. Pásala como LexMexTool(api_key=...) "
"o define la variable de entorno LEXMEX_API_KEY. Genera una en "
"https://lex-mex.xyz tras registrarte (plan VIP o pay-as-you-go)."
)
return key

def _run(self, pregunta: str) -> str:
resp = requests.post(
f"{self.api_base}/api/v1/consulta",
json={"pregunta": pregunta},
headers={"X-API-Key": self._resolved_key()},
timeout=self.timeout,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Do not allow callers to select the authenticated request host.

A caller can set api_base to an arbitrary URL. Line 97 then sends LEXMEX_API_KEY to that host. This enables API-key exfiltration and requests to internal network targets.

Use the fixed LEXMEX_API_BASE constant, or validate an HTTPS lex-mex.xyz allowlist before the request.

Proposed fix
-    api_base: str = LEXMEX_API_BASE
     timeout: int = 30
@@
-            f"{self.api_base}/api/v1/consulta",
+            f"{LEXMEX_API_BASE}/api/v1/consulta",
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
api_base: str = LEXMEX_API_BASE
timeout: int = 30
def _resolved_key(self) -> str:
key = self.api_key or os.getenv("LEXMEX_API_KEY")
if not key:
raise ValueError(
"Falta la API key de LEX-MEX. Pásala como LexMexTool(api_key=...) "
"o define la variable de entorno LEXMEX_API_KEY. Genera una en "
"https://lex-mex.xyz tras registrarte (plan VIP o pay-as-you-go)."
)
return key
def _run(self, pregunta: str) -> str:
resp = requests.post(
f"{self.api_base}/api/v1/consulta",
json={"pregunta": pregunta},
headers={"X-API-Key": self._resolved_key()},
timeout=self.timeout,
timeout: int = 30
def _resolved_key(self) -> str:
key = self.api_key or os.getenv("LEXMEX_API_KEY")
if not key:
raise ValueError(
"Falta la API key de LEX-MEX. Pásala como LexMexTool(api_key=...) "
"o define la variable de entorno LEXMEX_API_KEY. Genera una en "
"https://lex-mex.xyz tras registrarte (plan VIP o pay-as-you-go)."
)
return key
def _run(self, pregunta: str) -> str:
resp = requests.post(
f"{LEXMEX_API_BASE}/api/v1/consulta",
json={"pregunta": pregunta},
headers={"X-API-Key": self._resolved_key()},
timeout=self.timeout,
🧰 Tools
🪛 ast-grep (0.45.1)

[warning] 93-98: Request-controlled URL passed to requests; validate against an allowlist to prevent SSRF.
Context: requests.post(
f"{self.api_base}/api/v1/consulta",
json={"pregunta": pregunta},
headers={"X-API-Key": self._resolved_key()},
timeout=self.timeout,
)
Note: [CWE-918] Server-Side Request Forgery (SSRF).

(ssrf-requests)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/crewai-tools/src/crewai_tools/tools/lexmex_tool/lexmex_tool.py` around
lines 80 - 98, Prevent caller-controlled hosts in LexMexTool._run by removing
api_base from the request URL and always using the fixed LEXMEX_API_BASE
constant. Ensure the X-API-Key resolved by _resolved_key is sent only to the
trusted LEX-MEX endpoint.

Source: Linters/SAST tools

Comment on lines +13 to +36
def test_happy_path(monkeypatch):
tool = LexMexTool(api_key="lmx_live_test")

class FakeResponse:
status_code = 200

def raise_for_status(self):
pass

def json(self):
return {
"respuesta": "El despido justificado requiere...",
"fuentes": [{"cita": "LFT, art. 47"}],
"confianza": "alta",
}

monkeypatch.setattr(
"crewai_tools.tools.lexmex_tool.lexmex_tool.requests.post",
lambda *a, **kw: FakeResponse(),
)

resultado = tool.run(pregunta="¿Causales de despido justificado?")
assert "despido justificado" in resultado
assert "LFT, art. 47" in resultado

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Assert the outgoing LEX-MEX request contract.

The test passes if the endpoint, request body, API-key header, or timeout is changed incorrectly. Assert the mocked call so the test covers the behavior required to make a successful query.

Proposed fix
+from unittest.mock import Mock
+
 import pytest
@@
-    monkeypatch.setattr(
+    mock_post = Mock(return_value=FakeResponse())
+    monkeypatch.setattr(
         "crewai_tools.tools.lexmex_tool.lexmex_tool.requests.post",
-        lambda *a, **kw: FakeResponse(),
+        mock_post,
     )
@@
     assert "despido justificado" in resultado
     assert "LFT, art. 47" in resultado
+    mock_post.assert_called_once_with(
+        "https://lex-mex.xyz/api/v1/consulta",
+        json={"pregunta": "¿Causales de despido justificado?"},
+        headers={"X-API-Key": "lmx_live_test"},
+        timeout=30,
+    )

As per coding guidelines, **/*test*.py must test new functionality through behavior rather than implementation details.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def test_happy_path(monkeypatch):
tool = LexMexTool(api_key="lmx_live_test")
class FakeResponse:
status_code = 200
def raise_for_status(self):
pass
def json(self):
return {
"respuesta": "El despido justificado requiere...",
"fuentes": [{"cita": "LFT, art. 47"}],
"confianza": "alta",
}
monkeypatch.setattr(
"crewai_tools.tools.lexmex_tool.lexmex_tool.requests.post",
lambda *a, **kw: FakeResponse(),
)
resultado = tool.run(pregunta="¿Causales de despido justificado?")
assert "despido justificado" in resultado
assert "LFT, art. 47" in resultado
from unittest.mock import Mock
def test_happy_path(monkeypatch):
tool = LexMexTool(api_key="lmx_live_test")
class FakeResponse:
status_code = 200
def raise_for_status(self):
pass
def json(self):
return {
"respuesta": "El despido justificado requiere...",
"fuentes": [{"cita": "LFT, art. 47"}],
"confianza": "alta",
}
mock_post = Mock(return_value=FakeResponse())
monkeypatch.setattr(
"crewai_tools.tools.lexmex_tool.lexmex_tool.requests.post",
mock_post,
)
resultado = tool.run(pregunta="¿Causales de despido justificado?")
assert "despido justificado" in resultado
assert "LFT, art. 47" in resultado
mock_post.assert_called_once_with(
"https://lex-mex.xyz/api/v1/consulta",
json={"pregunta": "¿Causales de despido justificado?"},
headers={"X-API-Key": "lmx_live_test"},
timeout=30,
)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/crewai-tools/src/lib/crewai-tools/tests/tools/test_lexmex_tool.py` around
lines 13 - 36, Update test_happy_path to capture the mocked requests.post
invocation and assert the required LEX-MEX request contract: endpoint, request
body, API-key header, and timeout. Keep the existing response and output
assertions, using the mock call arguments to verify the successful query sends
the expected values.

Source: Coding guidelines

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.

1 participant