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
2 changes: 2 additions & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# The API reports are the public API contract: a change to one changes a public name or signature.
/packages/*/api-report/ @launchdarkly/team-ai-agents
8 changes: 8 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,14 @@ them mypy-clean under `strict` is a separate piece of work.
The `UP046` rule (PEP 695 `type` statement syntax for generic classes) is **ignored** in `pyproject.toml`. Do not convert `class Foo(Generic[T]):` to PEP 695 syntax — it is a deliberate migration decision.


## Public API Reports

Every public module has a committed API report at `packages/<package>/api-report/<module>.txt`: each name in its `__all__`, one per line, with its full signature, and a class's public members indented below it. The reports are the public API contract.

- **Change a public name or signature, then run `make api-report`** and commit the updated report in the same PR. `make test` fails while a report is out of date (`tests/test_api_report.py`), and so does `make api-report-check`.
- **The reports are owned by `@launchdarkly/team-ai-agents`** in `.github/CODEOWNERS`, so their approval of the report diff is how public API changes are reviewed.
- **Never edit a report by hand.** It is generated by `scripts/api_report.py` with griffe.

## PR Titles and the Changelog

PRs are squash-merged with the PR title as the commit message, and release-please builds each package's changelog from those messages.
Expand Down
10 changes: 9 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
.PHONY: start test typecheck lint format format-check lint-fix build clean
.PHONY: start test typecheck lint format format-check lint-fix build clean api-report api-report-check

# Run main.py (uv run python main.py)
start:
Expand All @@ -17,6 +17,14 @@ test:
typecheck:
uv run mypy packages/*/src

# Write every package's API report (packages/*/api-report/); commit the result
api-report:
uv run python scripts/api_report.py

# Fail if any API report is out of date (also run by `make test`)
api-report-check:
uv run python scripts/api_report.py --check

# Check for lint errors
lint:
uv run ruff check .
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Public API of launchdarkly_ai_claude_agents. Generated by `make api-report`; do not edit.
build_prompt(config: AiConfigRep, user_input: str | None, variables: dict[str, Any], history: list[dict[str, Any]] | None = None) -> tuple[str, str | None]
build_tool_mcp(config_tools: dict[str, Any], handlers: dict[str, Any]) -> Any [async]
claude_agents(config_key: str, user_input: str, context: LDContext, **kwargs: Any) -> Any
claude_graph(key: str, **options: Any) -> Any
ClaudeBash = NativeTool('Bash')
ClaudeEdit = NativeTool('Edit')
ClaudeGlob = NativeTool('Glob')
ClaudeGrep = NativeTool('Grep')
ClaudeNotebookEdit = NativeTool('NotebookEdit')
ClaudeRead = NativeTool('Read')
ClaudeTodoWrite = NativeTool('TodoWrite')
ClaudeWebFetch = NativeTool('WebFetch')
ClaudeWebSearch = NativeTool('WebSearch')
ClaudeWrite = NativeTool('Write')
create_claude_agents_handler(*, capture_content: bool | None = None) -> ProviderHandler
partition_tools(config_tools: dict[str, Any] | None, tool_handlers: dict[str, Any]) -> tuple[dict[str, Any], dict[str, Any], list[str]]
to_claude_agents(def_promise: Any, opts: dict[str, Any] | None = None) -> Any
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Public API of launchdarkly_ai_claude_messages. Generated by `make api-report`; do not edit.
claude_messages(config_key: str, user_input: str, context: LDContext, **kwargs: Any) -> Any
create_claude_messages_handler(*, capture_content: bool | None = None) -> ProviderHandler
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# Public API of launchdarkly_ai_server.experimental.evaluations. Generated by `make api-report`; do not edit.
AIConfig [class, @dataclass(frozen=True)]
__init__(key: str, variation: str)
key: str
variation: str
Criterion = Judge | Scorer [type]
DatasetRow [class, @dataclass]
__init__(row_index: int, input: str | None = None, expected_output: str | None = None, variables: dict[str, Any] = dict(), metadata: dict[str, Any] | None = None)
expected_output: str | None = None
input: str | None = None
metadata: dict[str, Any] | None = None
row_index: int
variables: dict[str, Any] = field(default_factory=dict)
EvalRunResult [class, @dataclass]
__init__(passed: bool, url: str, run_id: str, summary: RunSummary)
passed: bool
run_id: str
summary: RunSummary
url: str
EvalTool [class, @dataclass(frozen=True)]
__init__(key: str, implementation: ToolImplementation, schema: dict[str, Any], description: str = '')
description: str = ''
implementation: ToolImplementation
key: str
project_key: str | None = field(default=None, init=False)
schema: dict[str, Any]
source: Literal['library', 'inline'] = field(default='inline', init=False)
to_create_wire() -> dict[str, Any]
version: int | None = field(default=None, init=False)
EvaluationsError(Exception) [class]
EvaluationsModule [class]
__init__(api_client: LDApiClient, project_key: str, sdk_key: str | None, ui_base_uri: str = DEFAULT_UI_BASE_URI)
api: LDApiClient [property]
project_key: str [property]
run(*, key: str, dataset: str | Sequence[InlineDatasetRow], handler: EvalHandler | None = None, handlers: Sequence[EvalHandler] | None = None, generation: GenerationConfig | GenerationOverrides | None = None, ai_config: AIConfig | None = None, tools: Sequence[EvalTool] | None = None, criteria: list[Criterion] | None = None, concurrency: int = 10, poll_interval_seconds: float | None = None, poll_timeout_seconds: float | None = None) -> EvalRunResult [async]
sdk_key: str | None [property]
tools: ToolsClient [property]
ui_base_uri: str [property]
GenerationConfig(TypedDict, total=False) [class]
instructions: str
messages: list[dict[str, Any]]
mode: Required[GenerationMode]
model: Required[str]
output_format: dict[str, Any]
parameters: dict[str, Any]
prompt_snippets: dict[str, str]
provider: Required[str]
GenerationMode = Literal['completion', 'agent']
GenerationOverrides(TypedDict, total=False) [class]
instructions: str
messages: list[dict[str, Any]]
model: str
output_format: dict[str, Any]
parameters: dict[str, Any]
prompt_snippets: dict[str, str]
provider: str
init_evaluations(project_key: str | None = None, api_key: str | None = None, sdk_key: str | None = None, api_base_uri: str | None = None, ui_base_uri: str | None = None, transport: Transport = urllib_transport) -> EvaluationsModule
Judge [class, @dataclass(frozen=True)]
__init__(key: str, threshold: float | None = DEFAULT_JUDGE_THRESHOLD, pass_rate_threshold: float | None = None, ground_truth_context: str | None = None)
__post_init__() -> None
criterion_type: str [property]
ground_truth_context: str | None = None
key: str
pass_rate_threshold: float | None = None
threshold: float | None = DEFAULT_JUDGE_THRESHOLD
to_criteria_wire() -> dict[str, Any]
RunSummary [class, @dataclass]
__init__(total_rows: int = 0, passed_rows: int = 0, failed_rows: int = 0, error_rows: int = 0, pending_rows: int = 0)
error_rows: int = 0
failed_rows: int = 0
from_wire(data: Mapping[str, Any] | None) -> RunSummary [classmethod]
passed_rows: int = 0
pending_rows: int = 0
total_rows: int = 0
Scorer [class, @dataclass(frozen=True)]
__init__(name: str, fn: ScorerFn, threshold: float | None = 1.0, pass_rate_threshold: float | None = None, success_direction: SuccessDirection = 'higher_is_better')
__post_init__() -> None
criterion_type: str [property]
fn: ScorerFn
name: str
pass_rate_threshold: float | None = None
success_direction: SuccessDirection = 'higher_is_better'
threshold: float | None = 1.0
to_criteria_wire() -> dict[str, Any]
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# Public API of launchdarkly_ai_server.experimental.skills. Generated by `make api-report`; do not edit.
all_skills() -> list[Skill] [async]
FDv2SkillStore [class]
__init__(sdk_key: str, *, base_uri: str | None = None, stream_uri: str | None = None, mode: Mode = 'stream', poll_interval: float = 30.0, read_timeout: float | None = None, initial_backoff: float = 1.0, max_backoff: float = 30.0, _requester: Any = None)
__enter__() -> FDv2SkillStore
__exit__(*_exc: Any) -> None
add_listener(kind: str, fn: Callable[[dict[str, Any]], Any]) -> None
all_objects(kind: str) -> dict[str, dict[str, Any]]
close(timeout: float = 5.0) -> None
diagnostics: StoreDiagnostics [property]
failed: str | None [property]
get_object(kind: str, key: str, version: int | None = None) -> dict[str, Any] | None
is_initialized() -> bool
remove_listener(kind: str, fn: Callable[[dict[str, Any]], Any]) -> None
start() -> FDv2SkillStore
wait_for_skills(timeout: float = 10.0) -> bool
get_skill(key: str, *, version: int | None = None) -> Skill | None [async]
get_skill_result(key: str, *, version: int | None = None) -> SkillOutcome [async]
get_skills(refs: Sequence[SkillReference | str]) -> list[Skill] [async]
InMemorySkillStore [class]
__init__(objects: dict[str, dict[str, Any]] | None = None)
add_listener(kind: str, fn: Callable[[dict[str, Any]], Any]) -> None
all_objects(kind: str) -> dict[str, dict[str, Any]]
get_object(kind: str, key: str, version: int | None = None) -> dict[str, Any] | None
put(raw: dict[str, Any]) -> None
remove_listener(kind: str, fn: Callable[[dict[str, Any]], Any]) -> None
MANIFEST_FILENAME = '.launchdarkly-skills.json'
MANIFEST_VERSION = 1
OnUnavailable = Literal['keep', 'raise']
ReconcileAction [class, @dataclass(frozen=True)]
__init__(key: str, action: ReconcileActionKind, version: int | None = None, path: str | None = None, error: str | None = None)
action: ReconcileActionKind
error: str | None = None
key: str
path: str | None = None
version: int | None = None
ReconcileActionKind = Literal['written', 'updated', 'skipped_current', 'removed', 'error']
ReconcileReport [class, @dataclass(frozen=True)]
__init__(actions: list[ReconcileAction] = list())
actions: list[ReconcileAction] = field(default_factory=list)
errors: list[ReconcileAction] [property]
ok: bool [property]
set_skill_store(store: SkillStore | None) -> None
Skill [class, @dataclass(frozen=True)]
__init__(key: str, version: int, content: bytes, content_hash: str, name: str | None = None, description: str | None = None)
content: bytes
content_hash: str
description: str | None = None
key: str
name: str | None = None
version: int
SKILL_FILENAME = 'SKILL.md'
skill_refs(config: AiConfigRep | None) -> list[SkillReference]
SkillOutcome [class, @dataclass(frozen=True)]
__init__(skill: Skill | None, reason: SkillOutcomeReason, detail: str | None)
detail: str | None
reason: SkillOutcomeReason
skill: Skill | None
SkillOutcomeReason = Literal['absent', 'integrity_failure', 'ok', 'store_unavailable', 'wrong_version']
SkillReference [class, @dataclass(frozen=True)]
__init__(key: str, version: int)
key: str
version: int
SkillStore(Protocol) [class]
all_objects(kind: str) -> dict[str, dict[str, Any]]
get_object(kind: str, key: str, version: int | None = None) -> dict[str, Any] | None
SkillWatcher [class]
__init__(request: Sequence[Skill | SkillReference | str] | str, root: str | os.PathLike[str], store: Any, *, prune: bool, timeout: float, on_unavailable: OnUnavailable, debounce: float, on_reconcile: Callable[[ReconcileReport], Any] | None)
__enter__() -> SkillWatcher
__exit__(*_exc: Any) -> None
close(timeout: float = 15.0) -> None
notify(_raw: Any = None) -> None
reconciles: int [property]
StoreDiagnostics [class, @dataclass]
__init__(payloads_transferred: int = 0, skill_objects_received: int = 0, objects_ignored: int = 0, objects_revoked: int = 0, payloads_ignored: int = 0, hashless_objects: int = 0, connection_failures: int = 0, last_error: str | None = None)
connection_failures: int = 0
hashless_objects: int = 0
last_error: str | None = None
objects_ignored: int = 0
objects_revoked: int = 0
payloads_ignored: int = 0
payloads_transferred: int = 0
skill_objects_received: int = 0
watch_skills(skills: Sequence[Skill | SkillReference | str] | str, root: str | os.PathLike[str], *, prune: bool = True, timeout: float = 10.0, on_unavailable: OnUnavailable = 'keep', debounce: float = DEFAULT_DEBOUNCE_SECONDS, on_reconcile: Callable[[ReconcileReport], Any] | None = None) -> tuple[ReconcileReport, SkillWatcher] [async]
write_skills(skills: Sequence[Skill | SkillReference | str] | str, root: str | os.PathLike[str], *, prune: bool = True, timeout: float = 10.0, on_unavailable: OnUnavailable = 'keep') -> ReconcileReport [async]
Loading
Loading