File size: 3,134 Bytes
9fb3ca7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
"""Dictionary tool — word definitions via the Free Dictionary API.

No API key required. Returns definitions, part of speech, and examples.
API: https://dictionaryapi.dev/
"""

from __future__ import annotations

import logging
from typing import Any

import httpx

from app.tools.base_tool import BaseTool

logger = logging.getLogger(__name__)

_API_URL = "https://api.dictionaryapi.dev/api/v2/entries/en/{word}"
_TIMEOUT = 10.0


class DictionaryTool(BaseTool):
    """Look up word definitions, pronunciation, and examples."""

    @property
    def name(self) -> str:
        return "dictionary"

    @property
    def description(self) -> str:
        return (
            "Look up the definition of an English word. Returns meanings, "
            "part of speech, examples, and synonyms. "
            "Use for: 'define X', 'what does X mean', 'synonym of X'."
        )

    @property
    def input_schema(self) -> dict[str, Any]:
        return {
            "word": {
                "type": "string",
                "description": "The English word to look up.",
            }
        }

    def run(self, **kwargs: Any) -> str:
        word = kwargs.get("word", "").strip().lower()
        if not word:
            return "Error: No word provided."

        logger.info("[DICTIONARY] Looking up: %s", word)

        try:
            resp = httpx.get(
                _API_URL.format(word=word),
                timeout=_TIMEOUT,
                follow_redirects=True,
            )

            if resp.status_code == 404:
                return f"No definition found for '{word}'."
            if resp.status_code != 200:
                return f"Dictionary API error (HTTP {resp.status_code})."

            data = resp.json()
            if not data or not isinstance(data, list):
                return f"No definition found for '{word}'."

            return self._format(word, data[0])

        except Exception as exc:
            logger.warning("[DICTIONARY] Error: %s", exc)
            return f"Error looking up '{word}': {exc}"

    @staticmethod
    def _format(word: str, entry: dict) -> str:
        """Format the API response into a readable string."""
        parts: list[str] = [f"**{word}**"]

        # Phonetic
        phonetic = entry.get("phonetic", "")
        if phonetic:
            parts[0] += f"  ({phonetic})"

        meanings = entry.get("meanings", [])
        for meaning in meanings[:3]:  # max 3 parts of speech
            pos = meaning.get("partOfSpeech", "")
            definitions = meaning.get("definitions", [])

            if pos:
                parts.append(f"\n*{pos}*")

            for i, defn in enumerate(definitions[:2], 1):  # max 2 defs per POS
                text = defn.get("definition", "")
                example = defn.get("example", "")
                parts.append(f"  {i}. {text}")
                if example:
                    parts.append(f"     Example: \"{example}\"")

            synonyms = meaning.get("synonyms", [])[:5]
            if synonyms:
                parts.append(f"  Synonyms: {', '.join(synonyms)}")

        return "\n".join(parts)