mirror of
https://github.com/NCBM/plyngent.git
synced 2026-07-23 05:55:16 +08:00
ci/lint: prek commit gateway (ruff check/format + basedpyright)
Add prek.toml so git commit runs ruff-pre-commit (fix then format) and pdm-based basedpyright. Document install/install-hook; clear type warnings that made basedpyright exit non-zero under recommended mode.
This commit is contained in:
@@ -17,9 +17,14 @@ pdm run ruff format . # apply formatting
|
||||
pdm run ruff format --check . # CI: fail if unformatted (do not skip)
|
||||
pdm run basedpyright . # type checking (basedpyright, "recommended" strictness)
|
||||
pdm run pytest # tests (pytest-asyncio auto mode)
|
||||
|
||||
# Commit gateway (prek — https://prek.j178.dev/): ruff check/format + basedpyright
|
||||
uv tool install prek # once
|
||||
prek install # wire git pre-commit hook (once per clone)
|
||||
prek run --all-files # run all hooks on demand
|
||||
```
|
||||
|
||||
GitHub Actions runs `ruff check`, `ruff format --check`, `basedpyright`, then `pytest`. Format locally with `pdm run ruff format .` so CI does not fail after publish.
|
||||
GitHub Actions runs `ruff check`, `ruff format --check`, `basedpyright`, then `pytest`. Local commits run the same checks via `prek.toml` so `ruff format` is not forgotten.
|
||||
|
||||
## Architecture
|
||||
|
||||
|
||||
@@ -71,7 +71,15 @@ pdm run basedpyright .
|
||||
pdm run pytest
|
||||
```
|
||||
|
||||
CI fails if sources are not Ruff-formatted (`ruff format --check`). Run `pdm run ruff format .` before push.
|
||||
**Commit gateway** ([prek](https://prek.j178.dev/)): runs ruff check + format and basedpyright on `git commit` so format is not forgotten.
|
||||
|
||||
```bash
|
||||
uv tool install prek # once
|
||||
prek install # once per clone (installs .git/hooks/pre-commit)
|
||||
prek run --all-files # run all hooks on demand
|
||||
```
|
||||
|
||||
Config: `prek.toml`. CI still runs the same checks in GitHub Actions.
|
||||
|
||||
## Basic usage
|
||||
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
# Commit gateway via prek (https://prek.j178.dev/) — drop-in pre-commit compatible.
|
||||
# Install: uv tool install prek (or: pipx install prek)
|
||||
# Wire git: prek install
|
||||
# Manual: prek run --all-files
|
||||
|
||||
default_install_hook_types = ["pre-commit"]
|
||||
default_stages = ["pre-commit"]
|
||||
default_language_version = { python = "python3.14" }
|
||||
|
||||
# Ruff: lint (with autofix) then format — order matters (fix before format).
|
||||
# https://docs.astral.sh/ruff/integrations/#pre-commit
|
||||
[[repos]]
|
||||
repo = "https://github.com/astral-sh/ruff-pre-commit"
|
||||
rev = "v0.15.22"
|
||||
hooks = [
|
||||
{ id = "ruff-check", args = ["--fix"], types_or = ["python", "pyi"] },
|
||||
{ id = "ruff-format", types_or = ["python", "pyi"] },
|
||||
]
|
||||
|
||||
# basedpyright: full-project typecheck (same as CI `pdm run basedpyright .`).
|
||||
# Mirror hook: https://docs.basedpyright.com/latest/installation/prek-hook/
|
||||
# Uses system+PDM so imports resolve from the project venv (isolated hook envs lack deps).
|
||||
[[repos]]
|
||||
repo = "local"
|
||||
hooks = [
|
||||
{ id = "basedpyright", name = "basedpyright", language = "system", entry = "pdm run basedpyright .", pass_filenames = false, files = "\\.pyi?$" },
|
||||
]
|
||||
@@ -10,7 +10,6 @@ from plyngent.lmproto.openai_compatible.model import (
|
||||
ChatCompletionsParam,
|
||||
DeveloperChatMessage,
|
||||
SystemChatMessage,
|
||||
ToolChatMessage,
|
||||
UserChatMessage,
|
||||
)
|
||||
|
||||
@@ -44,7 +43,7 @@ _SEED_MESSAGE_TEMPLATE = (
|
||||
)
|
||||
|
||||
|
||||
def format_transcript(messages: Sequence[AnyChatMessage]) -> str: # noqa: C901
|
||||
def format_transcript(messages: Sequence[AnyChatMessage]) -> str:
|
||||
"""Render messages as plain text for a summarization prompt."""
|
||||
lines: list[str] = []
|
||||
for msg in messages:
|
||||
@@ -65,10 +64,9 @@ def format_transcript(messages: Sequence[AnyChatMessage]) -> str: # noqa: C901
|
||||
lines.append(f"[assistant tool_call] {call.function.name}({call.function.arguments})")
|
||||
else:
|
||||
lines.append(f"[assistant tool_call] custom id={call.id}")
|
||||
elif isinstance(msg, ToolChatMessage):
|
||||
lines.append(f"[tool {msg.tool_call_id}] {msg.content}")
|
||||
else:
|
||||
lines.append(f"[message] {msg!r}")
|
||||
# ToolChatMessage (remaining AnyChatMessage arm)
|
||||
lines.append(f"[tool {msg.tool_call_id}] {msg.content}")
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
|
||||
@@ -33,7 +33,6 @@ from plyngent.lmproto.openai_compatible.model import (
|
||||
StreamFunctionDelta,
|
||||
StreamToolCallDelta,
|
||||
SystemChatMessage,
|
||||
ToolChatMessage,
|
||||
ToolFunctionItem,
|
||||
UserChatMessage,
|
||||
)
|
||||
@@ -106,17 +105,14 @@ def chat_messages_to_responses_input(
|
||||
items.append(ResponseEasyInputMessage(role="user", content=message.content))
|
||||
elif isinstance(message, AssistantChatMessage):
|
||||
items.extend(_assistant_to_input_items(message))
|
||||
elif isinstance(message, ToolChatMessage):
|
||||
else:
|
||||
# ToolChatMessage (remaining AnyChatMessage arm)
|
||||
items.append(
|
||||
ResponseFunctionToolCallOutput(
|
||||
call_id=message.tool_call_id,
|
||||
output=message.content,
|
||||
)
|
||||
)
|
||||
else:
|
||||
content = getattr(message, "content", None)
|
||||
if isinstance(content, str) and content:
|
||||
items.append(ResponseEasyInputMessage(role="user", content=content))
|
||||
|
||||
instructions = "\n\n".join(instructions_parts) if instructions_parts else None
|
||||
return instructions, items
|
||||
@@ -173,13 +169,10 @@ def responses_status_to_finish_reason(
|
||||
status_s = status if isinstance(status, str) else None
|
||||
if status_s == "incomplete":
|
||||
details = response.incomplete_details
|
||||
reason = None
|
||||
if details is not UNSET and details is not None:
|
||||
raw_reason = details.reason
|
||||
if raw_reason is not UNSET and isinstance(raw_reason, str):
|
||||
reason = raw_reason
|
||||
if reason == "content_filter":
|
||||
return "content_filter"
|
||||
if raw_reason is not UNSET and raw_reason == "content_filter":
|
||||
return "content_filter"
|
||||
return "length"
|
||||
if status_s in {"failed", "cancelled"}:
|
||||
return status_s
|
||||
|
||||
@@ -95,7 +95,8 @@ class TodoStack:
|
||||
for frame in cast("list[object]", frames_raw):
|
||||
if not isinstance(frame, dict):
|
||||
continue
|
||||
frame_items = msgspec.convert(frame.get("items"), type=list[TodoItem])
|
||||
frame_map = cast("dict[str, object]", frame)
|
||||
frame_items = msgspec.convert(frame_map.get("items"), type=list[TodoItem])
|
||||
items.extend(frame_items)
|
||||
return cls(TodoStackData(items=items, next_id=next_id))
|
||||
except msgspec.ValidationError, TypeError, ValueError:
|
||||
|
||||
@@ -17,7 +17,6 @@ from plyngent.lmproto.openai_compatible.model import (
|
||||
AssistantFunctionToolCall,
|
||||
DeveloperChatMessage,
|
||||
SystemChatMessage,
|
||||
ToolChatMessage,
|
||||
UserChatMessage,
|
||||
)
|
||||
from plyngent.runtime import ProviderNotSupportedError
|
||||
@@ -36,7 +35,7 @@ _YOLO_MODE_CHOICES = ("on", "off", "once")
|
||||
_EXPORT_FORMAT_CHOICES = ("md", "json")
|
||||
|
||||
|
||||
class HistoryLimitType(click.ParamType):
|
||||
class HistoryLimitType(click.ParamType[int]):
|
||||
"""``N`` (int >= 1) or the shortcut ``last`` (equivalent to ``1``)."""
|
||||
|
||||
name: str = "history_limit"
|
||||
@@ -1100,15 +1099,9 @@ def _print_history_message_full(index: int, message: AnyChatMessage) -> None:
|
||||
if isinstance(message, AssistantChatMessage):
|
||||
_print_history_assistant_full(index, message)
|
||||
return
|
||||
if isinstance(message, ToolChatMessage):
|
||||
click.secho(f"{index}. tool({message.tool_call_id}):", fg="magenta")
|
||||
click.echo(message.content or "")
|
||||
click.echo()
|
||||
return
|
||||
role = getattr(message, "role", type(message).__name__)
|
||||
content = getattr(message, "content", "")
|
||||
click.echo(f"{index}. {role}:")
|
||||
click.echo(str(content))
|
||||
# ToolChatMessage (remaining AnyChatMessage arm)
|
||||
click.secho(f"{index}. tool({message.tool_call_id}):", fg="magenta")
|
||||
click.echo(message.content or "")
|
||||
click.echo()
|
||||
|
||||
|
||||
@@ -1134,11 +1127,8 @@ def _format_history_message(index: int, message: AnyChatMessage) -> str:
|
||||
parts.append(f"tool_calls=[{', '.join(names)}]")
|
||||
body = " ".join(parts) if parts else "(empty)"
|
||||
return f"{index}. assistant: {body}"
|
||||
if isinstance(message, ToolChatMessage):
|
||||
return f"{index}. tool({message.tool_call_id}): {_preview_content(message.content)}"
|
||||
role = getattr(message, "role", type(message).__name__)
|
||||
content = getattr(message, "content", "")
|
||||
return f"{index}. {role}: {_preview_content(str(content))}"
|
||||
# ToolChatMessage
|
||||
return f"{index}. tool({message.tool_call_id}): {_preview_content(message.content)}"
|
||||
|
||||
|
||||
def _run_slash_argv(args: Sequence[str], state: ReplState) -> None:
|
||||
|
||||
@@ -30,6 +30,7 @@ from plyngent.lmproto.openai_compatible.model import (
|
||||
ChatCompletionsParam,
|
||||
ChunkChoice,
|
||||
DeltaMessage,
|
||||
FinishReason,
|
||||
StreamFunctionDelta,
|
||||
StreamToolCallDelta,
|
||||
ToolChatMessage,
|
||||
@@ -174,8 +175,8 @@ class ScriptedClient:
|
||||
def _response(
|
||||
message: AssistantChatMessage,
|
||||
*,
|
||||
usage: dict[str, int] | None = None,
|
||||
finish_reason: str | None = "stop",
|
||||
usage: dict[str, object] | None = None,
|
||||
finish_reason: FinishReason | None = "stop",
|
||||
) -> ChatCompletionResponse:
|
||||
return ChatCompletionResponse(
|
||||
id="1",
|
||||
|
||||
Reference in New Issue
Block a user