Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions .github/workflows/contract.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,16 @@ jobs:
python-version: '3.14'
- run: uv sync --all-packages
# SDK features land on development before the platform deploys to
# prod, so PRs targeting development check against the dev spec.
# prod, so every PR but a release into main checks against the dev
# spec — including one stacked on another feature branch, which is
# just as far ahead of prod as the branch it targets.
# DEV_SPEC_URL is a repo secret; fork PRs don't receive it and
# fall back to the prod spec, keeping dev infra internal-only.
- env:
BASE_REF: ${{ github.base_ref }}
DEV_SPEC_URL: ${{ secrets.DEV_SPEC_URL }}
run: |
if [ "$BASE_REF" = "development" ] && [ -n "$DEV_SPEC_URL" ]; then
if [ "$BASE_REF" != "main" ] && [ -n "$DEV_SPEC_URL" ]; then
uv run python scripts/check_contract.py --spec-url "$DEV_SPEC_URL"
uv run python scripts/gen_requests.py --check --spec-url "$DEV_SPEC_URL"
else
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
- SDK: `Job` / `AsyncJob` returned by `validate_icp`, `discogen.process` and `discogen.process_personas` carry `column_name` from the submit response. The engine picks the columns — an LLM validation returns `Fit` / `Confidence` / `Reasoning`, the native model `ICP Fit` (`Yes` / `No` at a 0.50 threshold on the score) / `ICP Score` (the calibrated probability, 0.00-1.00 as a string) / `Reasoning (always null)` — so read it rather than hardcoding either set. It is `None` on a job reattached with `discogen.job(task_id)`.
- CLI: `validate-icp --integration-id native-icp` and `discogen run --integration-id native-icp` select the native ICP-fit model; `discolike validate-icp --help` documents both column sets.
- **Breaking:** the ICP-fit verdict is title case on every surface. An LLM `validate_icp` run's `Fit` column now returns `Yes` / `No` instead of `yes` / `no`, matching the native engine's `ICP Fit` column, DiscoGen and the app. Code that matches the verdict on the exact string `"yes"` has to be updated; compare case-insensitively to survive either.
- SDK/CLI: `discogen.process` and `discogen.process_personas` take `typed_columns`, and the CLI gains `--typed-columns`. It opts the query into the detector answering yes/no, fixed-set and scale columns with a TypeSafe judgment model instead of generated prose; which columns are typed and how is decided server-side from the query text, not passed in by the caller. Off by default. See `include_confidence` below for the sibling flag that adds a confidence column to each typed column.
- SDK/CLI: `discogen.process` and `discogen.process_personas` take `include_confidence`, and the CLI gains `--include-confidence`. It applies to typed columns, which a DiscoGen run answers with a TypeSafe judgment model rather than generated prose; with it on, each typed column gets a sibling confidence column. Off by default, and it changes display only - the answers and their probabilities come back either way.
- SDK: `contacts.generate` can run without an LLM key. Pass `integration_id=NATIVE_ENGINE` (exported from `discolike`) for DiscoLike Groove, DiscoLike's native extractor, or omit `integration_id` to get your default LLM integration and native when you have none. The native engine returns every person the search surfaces with a title and does not validate titles against `icp_text`; a search provider is still required. `JobStatus` gains `title_validation` (`"llm"` or `"none"`, `None` on non-ContaGen jobs) so you can tell which engine ran.
- SDK: discover and count take `sub_industry` / `negate_sub_industry` (217 second-level labels scoped to their parent industry category; a bare label like `ROOFING` adds `CONSTRUCTION` to `category` server-side) and a multi-shape geo filter: `geo` (`lat,lon` or `lat,lon,radius`), `bbox` (`min_lat,min_lon,max_lat,max_lon`, longitudes may wrap the antimeridian) and `lat` / `lon` / `radius` (`50km`, `30mi`, or a bare number meaning kilometres; defaults to 50km). `geo` and `bbox` are lists, every circle and box is OR'd with the others and with the `lat`/`lon` centre, and a query carries at most 10 shapes. Passing a single `bbox` or `geo` string still works and is sent as a one-element list. The SDK applies the platform's own shape rules before the request leaves the client: a box is four finite numbers with `-90 <= min_lat < max_lat <= 90`, longitudes within ±180 and `min_lon` different from `max_lon`; a circle is a valid coordinate pair with an optional radius above 0 and at most 1000km.
Expand Down
5 changes: 5 additions & 0 deletions packages/discolike-cli/src/discolike_cli/discogen.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
WEB_SEARCH_HELP = "Toggle web search during research."
CONTEXT_MODE_HELP = "Context mode; see docs.discolike.com."
INCLUDE_X_SEARCH_HELP = "Toggle including X search in the research."
TYPED_COLUMNS_HELP = "Let the detector answer yes/no, fixed-set and scale columns with a TypeSafe judgment model."
INCLUDE_CONFIDENCE_HELP = "Add a confidence column beside each typed column."
SEARCH_PROVIDER_ID_HELP = "Search provider ID to use for web search."
SEARCH_CONTEXT_SIZE_HELP = "Search context size; see docs.discolike.com."
Expand Down Expand Up @@ -63,6 +64,7 @@ def run_command(
include_x_search: bool | None = typer.Option(
None, "--include-x-search/--no-include-x-search", help=INCLUDE_X_SEARCH_HELP
),
typed_columns: bool | None = typer.Option(None, "--typed-columns/--no-typed-columns", help=TYPED_COLUMNS_HELP),
include_confidence: bool | None = typer.Option(
None, "--include-confidence/--no-include-confidence", help=INCLUDE_CONFIDENCE_HELP
),
Expand All @@ -88,6 +90,7 @@ def run_command(
web_search=web_search,
context_mode=context_mode,
include_x_search=include_x_search,
typed_columns=typed_columns,
include_confidence=include_confidence,
search_provider_id=search_provider_id,
search_context_size=search_context_size,
Expand All @@ -108,6 +111,7 @@ def run_personas_command(
include_x_search: bool | None = typer.Option(
None, "--include-x-search/--no-include-x-search", help=INCLUDE_X_SEARCH_HELP
),
typed_columns: bool | None = typer.Option(None, "--typed-columns/--no-typed-columns", help=TYPED_COLUMNS_HELP),
include_confidence: bool | None = typer.Option(
None, "--include-confidence/--no-include-confidence", help=INCLUDE_CONFIDENCE_HELP
),
Expand All @@ -130,6 +134,7 @@ def run_personas_command(
web_search=web_search,
context_mode=context_mode,
include_x_search=include_x_search,
typed_columns=typed_columns,
include_confidence=include_confidence,
search_provider_id=search_provider_id,
search_context_size=search_context_size,
Expand Down
42 changes: 42 additions & 0 deletions packages/discolike-cli/tests/test_discogen_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,26 @@ def handler(request: httpx2.Request) -> httpx2.Response:
}


def test_discogen_run_sends_typed_columns_when_passed(install_build_client: Callable[[Handler], None]) -> None:
captured: dict[str, object] = {}

def handler(request: httpx2.Request) -> httpx2.Response:
captured["body"] = json.loads(request.content)
return httpx2.Response(200, json={"task_id": "dg-1c"})

install_build_client(handler)
result = runner.invoke(
app,
["discogen", "run", "--query", "q", "--domain", "acme.com", "--typed-columns"],
)
assert result.exit_code == 0, result.output
assert captured["body"] == {
"query": "q",
"domains": ["acme.com"],
"typed_columns": True,
}


def test_discogen_run_sends_include_confidence_true_when_flag_passed(
install_build_client: Callable[[Handler], None],
) -> None:
Expand Down Expand Up @@ -175,6 +195,28 @@ def handler(request: httpx2.Request) -> httpx2.Response:
}


def test_discogen_run_personas_sends_typed_columns_when_passed(
install_build_client: Callable[[Handler], None],
) -> None:
captured: dict[str, object] = {}

def handler(request: httpx2.Request) -> httpx2.Response:
captured["body"] = json.loads(request.content)
return httpx2.Response(200, json={"task_id": "dg-2c"})

install_build_client(handler)
result = runner.invoke(
app,
["discogen", "run-personas", "--query", "q", "--persona-id", "1", "--typed-columns"],
)
assert result.exit_code == 0, result.output
assert captured["body"] == {
"query": "q",
"persona_ids": [1],
"typed_columns": True,
}


def test_discogen_run_personas_sends_include_confidence_true_when_flag_passed(
install_build_client: Callable[[Handler], None],
) -> None:
Expand Down
14 changes: 14 additions & 0 deletions packages/discolike/src/discolike/_generated/requests.py
Original file line number Diff line number Diff line change
Expand Up @@ -1427,6 +1427,13 @@ class DiscoGenProcessRequest(DiscolikeRequest):
] = None
web_search: Annotated[bool | None, Field(title="Web Search")] = False
include_x_search: Annotated[bool | None, Field(title="Include X Search")] = False
typed_columns: Annotated[
bool | None,
Field(
description="Let the detector answer yes/no, fixed-set and scale columns with a TypeSafe judgment model",
title="Typed Columns",
),
] = False
include_confidence: Annotated[bool | None, Field(title="Include Confidence")] = False
search_provider_id: Annotated[
str | None,
Expand Down Expand Up @@ -1463,6 +1470,13 @@ class DiscoGenPersonaProcessRequest(DiscolikeRequest):
] = None
web_search: Annotated[bool | None, Field(title="Web Search")] = False
include_x_search: Annotated[bool | None, Field(title="Include X Search")] = False
typed_columns: Annotated[
bool | None,
Field(
description="Let the detector answer yes/no, fixed-set and scale columns with a TypeSafe judgment model",
title="Typed Columns",
),
] = False
include_confidence: Annotated[bool | None, Field(title="Include Confidence")] = False
search_provider_id: Annotated[
str | None,
Expand Down
17 changes: 17 additions & 0 deletions packages/discolike/tests/test_discogen.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ def handler(request: httpx2.Request) -> httpx2.Response:
web_search=True,
context_mode="website",
include_x_search=False,
typed_columns=True,
include_confidence=True,
search_provider_id="serper",
search_context_size="medium",
)
Expand All @@ -84,11 +86,26 @@ def handler(request: httpx2.Request) -> httpx2.Response:
"web_search": True,
"context_mode": "website",
"include_x_search": False,
"typed_columns": True,
"include_confidence": True,
"search_provider_id": "serper",
"search_context_size": "medium",
}


def test_process_sends_typed_columns_when_set(make_client: ClientFactory) -> None:
seen = {}

def handler(request: httpx2.Request) -> httpx2.Response:
seen["body"] = json.loads(request.content)
return httpx2.Response(200, json={"task_id": "dg-typed"})

with make_client(handler) as client:
client.discogen.process(DiscoGenProcessRequest(query="q", domains=["a.com"], typed_columns=True))

assert seen["body"] == {"query": "q", "domains": ["a.com"], "typed_columns": True}


def test_process_personas_posts_json_and_returns_job(make_client: ClientFactory) -> None:
seen = {}

Expand Down
Loading