Skip to content

Commit c67044e

Browse files
committed
merged main
2 parents 5bd025b + b87afdd commit c67044e

File tree

278 files changed

+9464
-15077
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

278 files changed

+9464
-15077
lines changed

.agents/api_code_review.md

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
# FastAPI / OpenAPI Standards
2+
3+
Our OpenAPI spec drives our SDK, Scalar docs, and agent tool use (Kiln Chat calls our APIs). Every endpoint must be well-documented and consistently named. Flag violations during code review.
4+
5+
**Required on every endpoint:**
6+
7+
1. **`tags=[...]`** on the route decorator. Every endpoint must belong to a tag group (e.g. `tags=["Projects"]`). Untagged endpoints break Scalar navigation and agent tool discovery. Prefer existing tags, creating new ones only when really needed. All tags should be documented in `tags_metadata` in `server.py`
8+
2. **`summary=`** on the route decorator. A short, unique name for the operation. Summaries must be unambiguous — if two endpoints could share the same summary (e.g. "Edit Tags"), qualify them ("Edit Run Tags", "Edit Document Tags").
9+
3. **Docstring** on the handler function (optional if behavior is completely obvious from the path, method, and summary). When provided, docstrings should be terse — one sentence or a fragment. Never pad with filler like "This endpoint allows you to...". Longer descriptions (2–3 sentences) are warranted only when distinguishing easily confused endpoints, documenting non-obvious side effects, or noting prerequisites. Exclude if the `summary` string already covers the same level of detail.
10+
4. **`Path(description=...)`** on every path parameter, using `Annotated[str, Path(description="...")]` syntax. Recurring ID parameters must use consistent standard descriptions (e.g. `"The unique identifier of the project."`, `"The unique identifier of the task within the project."`).
11+
5. **`Query(description=...)`** on every query parameter.
12+
6. **`Field(description=...)`** on Pydantic model properties that aren't completely self-evident from name + type.
13+
7. **Class docstring** on Pydantic models used as API request/response bodies. These become the schema description in the OpenAPI spec, which agents and SDK users see when inspecting request/response types. Optional but suggested if non-obvious from name.
14+
15+
**Correct HTTP methods:**
16+
17+
- **GET** must be idempotent and side-effect-free. If an endpoint creates, modifies, or deletes data, it must not be GET. We previously had GET endpoints that established connections and ran evaluations — this is wrong and confuses both agents and humans.
18+
- **POST** for creation and actions that trigger execution.
19+
- **PATCH** for partial updates.
20+
- **DELETE** for deletion.
21+
- The only exception is SSE streaming endpoints, which must use GET due to browser `EventSource` constraints. These must have descriptions explicitly noting the mutation and the SSE reason.
22+
23+
**Naming and path conventions:**
24+
25+
- **Always use plural nouns** in path segments: `/tasks/{task_id}`, never `/task/{task_id}`. Same for `/projects`, `/specs`, `/evals`, `/runs`, `/prompts`, `/documents`, `/skills`, `/run_configs`, etc. We had inconsistencies where GET used plural but POST/PATCH/DELETE used singular — this is confusing and must be caught.
26+
- **Paths should be descriptive and intuitive.** Paths should follow REST conventions and be clear (as possible) without docstrings. Path and descriptions should distinguishing similar sounding endpoints. If a path could reasonably be improved, suggest a rename.
27+
- **Consistent path structure** for related resources. All operations on the same resource type should share a common path prefix (e.g. all run config operations under `/run_configs`, not split across `/task_run_config`, `/mcp_run_config`, `/run_config`). Important to not use similar but different prefixes, as this commonly trips up agents.
28+
- **No trailing slashes** on paths. Use `/run_configs` not `/run_configs/`. Trailing slashes cause inconsistency between endpoints and can break client routing.
29+
30+
**Example of a well-documented endpoint:**
31+
32+
```python
33+
@app.delete(
34+
"/api/projects/{project_id}",
35+
summary="Delete Project",
36+
tags=["Projects"],
37+
)
38+
async def delete_project(
39+
project_id: Annotated[
40+
str, Path(description="The unique identifier of the project.")
41+
],
42+
) -> dict:
43+
"""Removes the project from Kiln but does not delete the files from disk."""
44+
```
45+
46+
**What to flag in code review:**
47+
48+
- Missing `tags=` on any route decorator
49+
- Missing `summary=` on any route decorator
50+
- Missing `Path(description=...)` or `Query(description=...)` on any parameter
51+
- GET endpoints that perform mutations (unless SSE with documented justification)
52+
- Singular nouns in path segments where plural is standard
53+
- Ambiguous or duplicate summaries across endpoints
54+
- Trailing slashes on paths
55+
- Inconsistent path naming for the same resource type
56+
- Wordy or filler-padded docstrings ("This endpoint allows you to...")
57+
- Docstrings containing code artifacts, raw `Args:` blocks, or formatting that doesn't read as clean prose in OpenAPI
58+
- Pydantic models used in API request/response types (nested included) missing a class docstring, if the class name alone isn't obvious
59+
- Custom string types with validator-based constraints that don't surface in the OpenAPI schema. Use `StringConstraints` in the `Annotated` type definition so `minLength`/`maxLength` appear automatically (see `FilenameString`, `SkillNameString` for examples). Don't duplicate constraints in individual `Field()` calls.

.agents/code_review_guidelines.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,15 @@ The SDK in `/libs/core` is a SDK/library we expose to third parties. We code rev
2525
- All visible classes/vars should have docstrings explaining their purpose. These will be pulled into 3rd party docs automatically. The doc strings should be written for 3rd party devs learning the SDK.
2626
- Performance: the base_adapter and litellm_adapter are performance critical. They are the core run-loop of our agent system. We should avoid anything that would slow them down (file reads should be done once and passed in, etc). It's critical to avoid blocking IO - a process may be executing hundreds of these in parallel.
2727

28+
### FastAPI / OpenAPI Standards
29+
30+
If the change impacts API endpoints, read `.agents/api_code_review.md` for instructions on how to code review API endpoints.
31+
32+
Changes impacting APIs include:
33+
- adding/removing/modifying a FastAPI endpoint `@app.get`, `@app.delete`, etc
34+
- adding/removing/modifing a pydantic model which is used in an API endpoint, as a input/return value (including nested models)
35+
2836
### Project specific guide
2937

3038
- **`ModelName` enum and user input:** Do not use the `ModelName` enum for validation or typing of user-provided model identifiers (for example in a Pydantic request body that validates an API payload). Kiln loads additional models over the air; those models can use names that are not members of the locally shipped `ModelName` enum. If request validation is tied to the enum, a model that is valid according to the merged model list will fail validation. Appropriate uses of `ModelName` include aliasing a constant chosen at build time (for example default config that references a known shipped model) and entries inside the `ml_model_list` provider definitions.
39+

.coderabbit.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
reviews:
2+
path_filters:
3+
- "!app/desktop/studio_server/api_client/kiln_ai_server_client/**"

.github/workflows/check_api_bindings.yml

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,42 @@ jobs:
6060
# Change to the correct directory and run the schema check
6161
cd app/web_ui/src/lib
6262
./check_schema.sh
63+
cd -
64+
65+
# Check agent policy annotations
66+
ANNOTATIONS_DIR="libs/server/kiln_server/utils/agent_checks/annotations"
67+
TEMP_DIR=$(mktemp -d)
68+
69+
echo "Checking for unannotated endpoints..."
70+
uv run python -m kiln_server.utils.agent_checks.dump_annotations \
71+
http://localhost:8757/openapi.json "$TEMP_DIR"
72+
73+
echo "Checking annotation files are up to date..."
74+
DIFF_FAILED=false
75+
for f in "$TEMP_DIR"/*.json; do
76+
filename=$(basename "$f")
77+
if [ ! -f "$ANNOTATIONS_DIR/$filename" ]; then
78+
echo "Missing checked-in annotation: $filename"
79+
DIFF_FAILED=true
80+
elif ! diff -q "$f" "$ANNOTATIONS_DIR/$filename" > /dev/null 2>&1; then
81+
echo "Annotation differs: $filename"
82+
diff -u "$ANNOTATIONS_DIR/$filename" "$f" || true
83+
DIFF_FAILED=true
84+
fi
85+
done
86+
87+
if [ "$DIFF_FAILED" = true ]; then
88+
echo ""
89+
echo -e "\033[31mAgent policy annotations are not up to date.\033[0m"
90+
echo "Run the dump CLI to regenerate:"
91+
echo " uv run python -m kiln_server.utils.agent_checks.dump_annotations http://localhost:8757/openapi.json $ANNOTATIONS_DIR"
92+
rm -rf "$TEMP_DIR"
93+
kill $DEV_SERVER_PID || true
94+
exit 1
95+
fi
96+
97+
echo "Agent policy annotations are up to date."
98+
rm -rf "$TEMP_DIR"
6399
64100
# Stop dev server
65101
kill $DEV_SERVER_PID || true

app/desktop/studio_server/api_models/copilot_models.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,18 @@
88
class TaskInfoApi(BaseModel):
99
"""Task information for copilot API calls."""
1010

11-
task_prompt: str
12-
task_input_schema: str
13-
task_output_schema: str
11+
task_prompt: str = Field(description="The task's prompt.")
12+
task_input_schema: str = Field(description="The task's input JSON schema.")
13+
task_output_schema: str = Field(description="The task's output JSON schema.")
1414

1515

1616
class TaskMetadataApi(BaseModel):
1717
"""Metadata about the model used for a task."""
1818

19-
model_name: str
20-
model_provider_name: ModelProviderName
19+
model_name: str = Field(description="The name of the AI model used.")
20+
model_provider_name: ModelProviderName = Field(
21+
description="The provider hosting the model (e.g. OpenAI, Anthropic)."
22+
)
2123

2224

2325
class SyntheticDataGenerationStepConfigApi(BaseModel):

app/desktop/studio_server/copilot_api.py

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import logging
2+
from typing import Annotated
23

34
from app.desktop.studio_server.api_client.kiln_ai_server_client.api.copilot import (
45
clarify_spec_v1_copilot_clarify_spec_post,
@@ -47,7 +48,7 @@
4748
get_copilot_api_key,
4849
)
4950
from app.desktop.studio_server.utils.response_utils import unwrap_response
50-
from fastapi import FastAPI, HTTPException
51+
from fastapi import FastAPI, HTTPException, Path
5152
from kiln_ai.datamodel import TaskRun
5253
from kiln_ai.datamodel.basemodel import FilenameString
5354
from kiln_ai.datamodel.datamodel_enums import Priority
@@ -74,6 +75,7 @@
7475
RefineSpecApiOutput,
7576
SubmitAnswersRequest,
7677
)
78+
from kiln_server.utils.agent_checks.policy import ALLOW_AGENT
7779
from pydantic import BaseModel, Field
7880

7981
logger = logging.getLogger(__name__)
@@ -113,7 +115,7 @@ class CreateSpecWithCopilotRequest(BaseModel):
113115

114116

115117
def connect_copilot_api(app: FastAPI):
116-
@app.post("/api/copilot/clarify_spec")
118+
@app.post("/api/copilot/clarify_spec", tags=["Copilot"], openapi_extra=ALLOW_AGENT)
117119
async def clarify_spec(input: ClarifySpecApiInput) -> ClarifySpecApiOutput:
118120
api_key = get_copilot_api_key()
119121
client = get_authenticated_client(api_key)
@@ -139,7 +141,7 @@ async def clarify_spec(input: ClarifySpecApiInput) -> ClarifySpecApiOutput:
139141
detail="Unknown error.",
140142
)
141143

142-
@app.post("/api/copilot/refine_spec")
144+
@app.post("/api/copilot/refine_spec", tags=["Copilot"], openapi_extra=ALLOW_AGENT)
143145
async def refine_spec(input: RefineSpecApiInput) -> RefineSpecApiOutput:
144146
api_key = get_copilot_api_key()
145147
client = get_authenticated_client(api_key)
@@ -165,7 +167,9 @@ async def refine_spec(input: RefineSpecApiInput) -> RefineSpecApiOutput:
165167
detail="Unknown error.",
166168
)
167169

168-
@app.post("/api/copilot/generate_batch")
170+
@app.post(
171+
"/api/copilot/generate_batch", tags=["Copilot"], openapi_extra=ALLOW_AGENT
172+
)
169173
async def generate_batch(input: GenerateBatchApiInput) -> GenerateBatchApiOutput:
170174
api_key = get_copilot_api_key()
171175
client = get_authenticated_client(api_key)
@@ -191,7 +195,7 @@ async def generate_batch(input: GenerateBatchApiInput) -> GenerateBatchApiOutput
191195
detail="Unknown error.",
192196
)
193197

194-
@app.post("/api/copilot/question_spec")
198+
@app.post("/api/copilot/question_spec", tags=["Copilot"], openapi_extra=ALLOW_AGENT)
195199
async def question_spec(
196200
input: SpecQuestionerApiInput,
197201
) -> QuestionSet:
@@ -219,7 +223,11 @@ async def question_spec(
219223
detail="Unknown error.",
220224
)
221225

222-
@app.post("/api/copilot/refine_spec_with_question_answers")
226+
@app.post(
227+
"/api/copilot/refine_spec_with_question_answers",
228+
tags=["Copilot"],
229+
openapi_extra=ALLOW_AGENT,
230+
)
223231
async def submit_question_answers(
224232
request: SubmitAnswersRequest,
225233
) -> RefineSpecApiOutput:
@@ -245,9 +253,20 @@ async def submit_question_answers(
245253
detail="Unknown error.",
246254
)
247255

248-
@app.post("/api/projects/{project_id}/tasks/{task_id}/spec_with_copilot")
256+
@app.post(
257+
"/api/projects/{project_id}/tasks/{task_id}/spec_with_copilot",
258+
tags=["Copilot"],
259+
openapi_extra=ALLOW_AGENT,
260+
)
249261
async def create_spec_with_copilot(
250-
project_id: str, task_id: str, request: CreateSpecWithCopilotRequest
262+
project_id: Annotated[
263+
str, Path(description="The unique identifier of the project.")
264+
],
265+
task_id: Annotated[
266+
str,
267+
Path(description="The unique identifier of the task within the project."),
268+
],
269+
request: CreateSpecWithCopilotRequest,
251270
) -> Spec:
252271
"""Create a spec using Kiln Copilot.
253272

0 commit comments

Comments
 (0)