diff --git a/ddls_extracted/evals_employees_DDL.sql b/ddls_extracted/evals_employees_DDL.sql deleted file mode 100644 index 935ffe2..0000000 --- a/ddls_extracted/evals_employees_DDL.sql +++ /dev/null @@ -1,25 +0,0 @@ -/* - * File: evals_employees_DDL.sql - * Generated: 2026-06-23 14:16:55 - * Type: TABLE - * Database: demo_user - * Object: evals_employees - * Size: 572 characters - */ - -CREATE SET TABLE demo_user.evals_employees ,FALLBACK , - NO BEFORE JOURNAL, - NO AFTER JOURNAL, - CHECKSUM = DEFAULT, - DEFAULT MERGEBLOCKRATIO, - MAP = TD_MAP1 - ( - employee_id INTEGER NOT NULL, - name VARCHAR(100) CHARACTER SET LATIN NOT CASESPECIFIC NOT NULL, - department VARCHAR(50) CHARACTER SET LATIN NOT CASESPECIFIC NOT NULL, - salary DECIMAL(10,2), - region VARCHAR(50) CHARACTER SET LATIN NOT CASESPECIFIC NOT NULL, - hire_date DATE FORMAT 'YY/MM/DD' NOT NULL, - manager_id INTEGER, -PRIMARY KEY ( employee_id )) -; \ No newline at end of file diff --git a/ddls_extracted/evals_orders_DDL.sql b/ddls_extracted/evals_orders_DDL.sql deleted file mode 100644 index 8c90da9..0000000 --- a/ddls_extracted/evals_orders_DDL.sql +++ /dev/null @@ -1,25 +0,0 @@ -/* - * File: evals_orders_DDL.sql - * Generated: 2026-06-23 14:16:30 - * Type: TABLE - * Database: demo_user - * Object: evals_orders - * Size: 563 characters - */ - -CREATE SET TABLE demo_user.evals_orders ,FALLBACK , - NO BEFORE JOURNAL, - NO AFTER JOURNAL, - CHECKSUM = DEFAULT, - DEFAULT MERGEBLOCKRATIO, - MAP = TD_MAP1 - ( - order_id INTEGER NOT NULL, - customer_name VARCHAR(100) CHARACTER SET LATIN NOT CASESPECIFIC NOT NULL, - order_date DATE FORMAT 'YY/MM/DD' NOT NULL, - ship_date DATE FORMAT 'YY/MM/DD', - amount DECIMAL(10,2) NOT NULL, - product_category VARCHAR(50) CHARACTER SET LATIN NOT CASESPECIFIC NOT NULL, - quantity INTEGER NOT NULL, -PRIMARY KEY ( order_id )) -; \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index ca299dd..9a45208 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,6 +24,7 @@ classifiers = [ ] dependencies = [ "pydantic>=2.8.0,<3.0.0", + "pydantic-settings>=2.14.2", "fastmcp>=3.2.0,<3.4.2", "mcp[cli]>=1.0.0,<2.0.0", "teradatasqlalchemy>=20.0.0.0", @@ -31,6 +32,7 @@ dependencies = [ "PyYAML>=6.0.0", "sqlalchemy>=2.0.0,<3.0.0", "starlette>=1.3.1", + "langsmith>=0.8.18", ] [project.optional-dependencies] diff --git a/scripts/seed_tdml_summaries.py b/scripts/seed_tdml_summaries.py deleted file mode 100644 index 20bc392..0000000 --- a/scripts/seed_tdml_summaries.py +++ /dev/null @@ -1,198 +0,0 @@ -""" -Seed script: connects to Teradata, extracts one-line summaries from teradataml -__init__.__doc__ for all TD_ANALYTIC_FUNCS, then prints the new dict[str, str] -block ready to paste into constants.py. - -Requires DATABASE_URI env var: - export DATABASE_URI="teradata://user:pass@host:1025/db" - uv run python scripts/seed_tdml_summaries.py -""" - -import os -import re -import textwrap -import warnings - -warnings.filterwarnings("ignore") - -import teradataml as tdml # noqa: E402 - -# Connect so that teradataml populates __init__.__doc__ on each class -_uri = os.environ.get("DATABASE_URI", "") -if _uri: - _m = re.match(r"teradata://([^:]+):([^@]+)@([^:]+):(\d+)/(.+)", _uri) - if _m: - tdml.create_context( - host=_m.group(3), - username=_m.group(1), - password=_m.group(2), - database=_m.group(5), - ) - else: - raise ValueError(f"Cannot parse DATABASE_URI: {_uri}") -else: - raise EnvironmentError("DATABASE_URI not set — docstrings require a live connection") - -FUNCS = [ - "ANOVA", - "Attribution", - "Antiselect", - "Apriori", - "BincodeFit", - "BincodeTransform", - "CFilter", - "CategoricalSummary", - "ChiSq", - "ClassificationEvaluator", - "ColumnSummary", - "ColumnTransformer", - "ConvertTo", - "DecisionForest", - "FTest", - "FillRowId", - "Fit", - "GetFutileColumns", - "GetRowsWithMissingValues", - "GetRowsWithoutMissingValues", - "GLM", - "GLMPerSegment", - "Histogram", - "KMeans", - "KMeansPredict", - "KNN", - "MovingAverage", - "NERExtractor", - "NGramSplitter", - "NaiveBayesTextClassifierPredict", - "NaiveBayesTextClassifierTrainer", - "NonLinearCombineFit", - "NonLinearCombineTransform", - "NumApply", - "NPath", - "OneClassSVM", - "OneClassSVMPredict", - "OneHotEncodingFit", - "OneHotEncodingTransform", - "OrdinalEncodingFit", - "OrdinalEncodingTransform", - "OutlierFilterFit", - "OutlierFilterTransform", - "Pack", - "PolynomialFeaturesFit", - "PolynomialFeaturesTransform", - "Pivoting", - "QQNorm", - "ROC", - "RandomProjectionFit", - "RandomProjectionMinComponents", - "RandomProjectionTransform", - "RegressionEvaluator", - "RoundColumns", - "RowNormalizeFit", - "RowNormalizeTransform", - "SMOTE", - "SVM", - "SVMPredict", - "ScaleFit", - "ScaleTransform", - "Sessionize", - "SentimentExtractor", - "Shap", - "Silhouette", - "SimpleImputeFit", - "SimpleImputeTransform", - "StrApply", - "StringSimilarity", - "TDDecisionForestPredict", - "TDGLMPredict", - "TDNaiveBayesPredict", - "TFIDF", - "TargetEncodingFit", - "TargetEncodingTransform", - "TextMorph", - "TextParser", - "TrainTestSplit", - "Transform", - "UnivariateStatistics", - "Unpack", - "Unpivoting", - "VectorDistance", - "WhichMax", - "WhichMin", - "WordEmbeddings", - "XGBoost", - "XGBoostPredict", - "ZTest", -] - - -def extract_summary(func_name: str) -> str: - """Pull the first meaningful sentence from the teradataml __init__ docstring.""" - func_obj = getattr(tdml, func_name, None) - if func_obj is None: - return f"Teradata ML analytic function {func_name}." - - raw = getattr(func_obj.__init__, "__doc__", None) or "" - # Dedent and strip leading blank lines - raw = textwrap.dedent(raw).strip() - - # The teradataml pattern is: - # DESCRIPTION: - # - # - # PARAMETERS: - # Try to grab the DESCRIPTION block first. - desc_match = re.search(r"DESCRIPTION\s*:\s*\n(.*?)(?:\n\s*\n|\n\s*PARAMETERS\s*:)", raw, re.DOTALL) - if desc_match: - block = desc_match.group(1) - else: - # Fallback: take the first non-empty paragraph - block = raw.split("\n\n")[0] - - # Collapse internal whitespace / newlines into a single line - block = re.sub(r"\s+", " ", block).strip() - - # Replace teradataml-specific terminology - block = block.replace("teradataml DataFrame", "table name") - block = block.replace("DataFrame", "table name") - - # Truncate at the first sentence boundary (period followed by space or end) - # Keep the trailing period. - sent_match = re.search(r"^(.*?\.)\s", block) - if sent_match: - summary = sent_match.group(1) - else: - # No sentence boundary — use the whole block but cap length - summary = block[:200].rstrip() - if not summary.endswith("."): - summary += "." - - return summary - - -def main(): - results: list[tuple[str, str]] = [] - missing: list[str] = [] - - for name in FUNCS: - summary = extract_summary(name) - results.append((name, summary)) - if "analytic function" in summary and name in summary: - missing.append(name) - - # Print the dict literal ready to paste into constants.py - print("TD_ANALYTIC_FUNCS = {") - for name, summary in results: - # Escape any quotes inside the summary - safe = summary.replace('"', '\\"') - print(f' "{name}": "{safe}",') - print("}") - - if missing: - print(f"\n# WARNING: {len(missing)} functions had no extractable docstring — fallback used:") - for m in missing: - print(f"# {m}") - - -if __name__ == "__main__": - main() diff --git a/uv.lock b/uv.lock index bebc348..867f9ec 100644 --- a/uv.lock +++ b/uv.lock @@ -2046,23 +2046,27 @@ wheels = [ [[package]] name = "langsmith" -version = "0.8.16" +version = "0.9.1" source = { registry = "https://pypi.org/simple" } dependencies = [ + { name = "anyio" }, + { name = "distro" }, { name = "httpx" }, { name = "orjson", marker = "platform_python_implementation != 'PyPy'" }, { name = "packaging" }, { name = "pydantic" }, { name = "requests" }, { name = "requests-toolbelt" }, + { name = "sniffio" }, + { name = "typing-extensions" }, { name = "uuid-utils" }, { name = "websockets" }, { name = "xxhash" }, { name = "zstandard" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6f/19/1ed2af9c6d5d7a148e6b3e809b0af8ce8848e1f66a0726c8223d30e5292b/langsmith-0.8.16.tar.gz", hash = "sha256:8c943f0c9185fe2a9637b5b442828b7efd823b1de28d50d14c136c79660f909b", size = 4513275 } +sdist = { url = "https://files.pythonhosted.org/packages/5b/26/b72987d947278f63ec1e85f01ce85ca7ab2621c7efc0845d4a3a8e5d5dfb/langsmith-0.9.1.tar.gz", hash = "sha256:e5eb905224d156bcece4985285c55b51fffcb06c9353b2c4adb42e1c48b0d05d", size = 4557557 } wheels = [ - { url = "https://files.pythonhosted.org/packages/c3/13/8186a9867c67f3fef9958a1d60b45f46c1a9b5d28f67d8fd136f28ceab3f/langsmith-0.8.16-py3-none-any.whl", hash = "sha256:081e57c0175d142192683288740a796eb0eb32d9e703b4bf9133678ceefe3286", size = 500303 }, + { url = "https://files.pythonhosted.org/packages/63/42/13c67eb24cddb368df2c8af13e319c86b1d270c90f5b8c9e8baed3593e04/langsmith-0.9.1-py3-none-any.whl", hash = "sha256:1160bf667af63d9bc081821f1df351cb84f7875740858f2a97ffef62b21800a9", size = 578856 }, ] [[package]] @@ -3674,16 +3678,16 @@ wheels = [ [[package]] name = "pydantic-settings" -version = "2.14.1" +version = "2.14.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pydantic" }, { name = "python-dotenv" }, { name = "typing-inspection" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/07/60/1d1e59c9c90d54591469ada7d268251f71c24bdb765f1a8a832cee8c6653/pydantic_settings-2.14.1.tar.gz", hash = "sha256:e874d3bec7e787b0c9958277956ed9b4dd5de6a80e162188fdaff7c5e26fd5fa", size = 235551 } +sdist = { url = "https://files.pythonhosted.org/packages/5c/b5/8f48e906c3e0205276e8bd8cb7512217a87b2685304d64be27cad5b3019f/pydantic_settings-2.14.2.tar.gz", hash = "sha256:c19dd64b19097f1de80184f0cc7b0272a13ae6e170cbf240a3e27e381ed14a5f", size = 237700 } wheels = [ - { url = "https://files.pythonhosted.org/packages/ae/8d/f1af3832f5e6eb13ba94ee809e72b8ecb5eef226d27ee0bef7d963d943c7/pydantic_settings-2.14.1-py3-none-any.whl", hash = "sha256:6e3c7edfd8277687cdc598f56e5cff0e9bfff0910a3749deaa8d4401c3a2b9de", size = 60964 }, + { url = "https://files.pythonhosted.org/packages/77/c1/6e422f34e569cf8e18df68d1939c81c099d2b61e4f7d9621c8a77560799c/pydantic_settings-2.14.2-py3-none-any.whl", hash = "sha256:a20c97b37910b6550d5ea50fbcc2d4187defe58cd57070b73863d069419c9440", size = 61715 }, ] [[package]] @@ -4458,8 +4462,10 @@ version = "0.2.4" source = { editable = "." } dependencies = [ { name = "fastmcp" }, + { name = "langsmith" }, { name = "mcp", extra = ["cli"] }, { name = "pydantic" }, + { name = "pydantic-settings" }, { name = "python-dotenv" }, { name = "pyyaml" }, { name = "sqlalchemy" }, @@ -4492,9 +4498,11 @@ dev = [ [package.metadata] requires-dist = [ { name = "fastmcp", specifier = ">=3.2.0,<3.4.2" }, + { name = "langsmith", specifier = ">=0.8.18" }, { name = "mcp", extras = ["cli"], specifier = ">=1.0.0,<2.0.0" }, { name = "mypy", marker = "extra == 'dev'", specifier = ">=1.5.0" }, { name = "pydantic", specifier = ">=2.8.0,<3.0.0" }, + { name = "pydantic-settings", specifier = ">=2.14.2" }, { name = "python-dotenv", specifier = ">=1.0.0" }, { name = "pyyaml", specifier = ">=6.0.0" }, { name = "requests", marker = "extra == 'bar'", specifier = ">=2.25.0" },