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: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ jobs:

# setup-uv publishes no moving major tag past v7, so this is pinned to an
# exact release. Dependabot's github-actions entry keeps it current.
- uses: astral-sh/setup-uv@v10.2.0
- uses: astral-sh/setup-uv@v10.3.3
with:
enable-cache: true
cache-dependency-glob: ${{ matrix.project }}/uv.lock
Expand Down
20 changes: 10 additions & 10 deletions chatbot/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,30 +6,30 @@ requires-python = ">=3.14"
dependencies = [
# Anthropic's Python SDK, behind the claude mode. Optional at runtime in the
# sense that matters: with no API key the mode says so and the rest works.
"anthropic>=1.9.0",
"fastapi>=0.142.2",
"anthropic>=2.3.0",
"fastapi>=0.162.0",
# Backs starlette's Jinja2Templates
"jinja2>=3.1.6",
"pydantic>=2.13.5",
"pydantic-settings>=2.15.0",
"pydantic>=2.14.2",
"pydantic-settings>=2.16.2",
"uvicorn>=0.54.0",
# uvicorn's WebSocket protocol implementation. The test client does not use
# it, so tests pass without it while the real server rejects upgrades.
"websockets>=17.1",
"websockets>=17.4",
]

[dependency-groups]
test = [
# starlette's TestClient imports httpx2, and anthropic 1.x builds its errors
# from httpx2 objects, which the claude client tests construct by hand. The
# starlette's TestClient imports httpx2, and anthropic (1.x and 2.x) builds its
# errors from httpx2 objects, which the claude client tests construct by hand. The
# first httpx generation is gone from this project with anthropic 0.x.
"httpx2>=2.13.1",
"pytest>=9.1.1",
"httpx2>=2.16.0",
"pytest>=9.2.0",
"pytest-cov>=7.1.0",
]
dev = [
{ include-group = "test" },
"ruff>=0.16.9",
"ruff>=0.17.3",
]

[tool.uv]
Expand Down
742 changes: 429 additions & 313 deletions chatbot/uv.lock

Large diffs are not rendered by default.

7 changes: 6 additions & 1 deletion lemon/compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,12 @@ services:
volumes:
- mysql-data:/var/lib/mysql
healthcheck:
test: ["CMD-SHELL", "mysqladmin ping -h localhost -uroot -p$${MYSQL_ROOT_PASSWORD} --silent"]
# Over TCP, not the socket that `-h localhost` means. On an empty volume
# the image first runs a temporary server with networking off while it
# creates the database; a socket ping passes against that one, so
# `--wait` returned before anything listened on 3306 and the tests lost
# the connection (MySQL error 2013). Only the real server answers on TCP.
test: ["CMD-SHELL", "mysqladmin ping -h 127.0.0.1 --protocol=tcp -uroot -p$${MYSQL_ROOT_PASSWORD} --silent"]
interval: 5s
timeout: 5s
retries: 20
Expand Down
8 changes: 4 additions & 4 deletions lemon/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ version = "0.1.0"
description = "Little Lemon restaurant backend built with Django and Django REST Framework."
requires-python = ">=3.14"
dependencies = [
"django>=6.1.1",
"django>=6.1.2",
"django-environ>=0.14.0",
"djangorestframework>=3.18.3",
# Lemon mounts djoser's user and token URLs only. Its social login cannot
Expand All @@ -13,20 +13,20 @@ dependencies = [
# validates it again and finds it spent. Resolve that before mounting
# djoser.social.urls.
"djoser>=2.3.5",
"markdown>=3.11",
"markdown>=3.11.2",
"mysqlclient>=2.3.0",
]

[dependency-groups]
test = [
"pytest>=9.1.1",
"pytest>=9.2.0",
"pytest-django>=4.14.0",
"pytest-cov>=7.1.0",
]
dev = [
{ include-group = "test" },
"django-debug-toolbar>=8.0.0",
"ruff>=0.16.9",
"ruff>=0.17.3",
]

[tool.uv]
Expand Down
533 changes: 277 additions & 256 deletions lemon/uv.lock

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions mathparser/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ dependencies = []

[dependency-groups]
test = [
"pytest>=9.1.1",
"pytest>=9.2.0",
"pytest-cov>=7.1.0",
]
dev = [
{ include-group = "test" },
"ruff>=0.16.9",
"ruff>=0.17.3",
]

[tool.uv]
Expand Down
196 changes: 102 additions & 94 deletions mathparser/uv.lock

Large diffs are not rendered by default.

14 changes: 7 additions & 7 deletions mcp/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,24 @@ description = "Command-line MCP chat client and document server built on the Ant
readme = "README.md"
requires-python = ">=3.14"
dependencies = [
"anthropic>=1.9.0",
"mcp[cli]>=2.2.0",
"anthropic>=2.3.0",
"mcp[cli]>=2.3.0",
"prompt-toolkit>=3.0.53",
# Declares the tool schemas in mcp_server.py. It arrives through the mcp
# SDK as well, but this project imports it directly.
"pydantic>=2.13.5",
"python-dotenv>=1.2.3",
"pydantic>=2.14.2",
"python-dotenv>=1.2.4",
]

[dependency-groups]
test = [
"pytest>=9.1.1",
"pytest-asyncio>=1.4.0",
"pytest>=9.2.0",
"pytest-asyncio>=1.4.1.post1",
"pytest-cov>=7.1.0",
]
dev = [
{ include-group = "test" },
"ruff>=0.16.9",
"ruff>=0.17.3",
]

[tool.uv]
Expand Down
4 changes: 2 additions & 2 deletions mcp/src/core/claude.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,17 +28,17 @@ def chat(
self,
messages,
system=None,
temperature=1.0,
stop_sequences=None,
tools=None,
thinking=False,
thinking_budget=1024,
) -> Message:
# No temperature, top_p or top_k: API version 2026-10-21, which anthropic
# 2.x sends, rejects a request carrying any of them, even at the default.
params = {
"model": self.model,
"max_tokens": 8000,
"messages": messages,
"temperature": temperature,
"stop_sequences": stop_sequences or [],
}

Expand Down
14 changes: 11 additions & 3 deletions mcp/src/core/cli_chat.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,14 @@ async def _process_query(self, query: str):
self.messages.append({"role": "user", "content": prompt})


def _message_param(role: str, content) -> MessageParam:
# anthropic 2.x types MessageParam as one TypedDict per role, so a dict whose
# role is a variable matches none of them: each role gets its own literal.
if role == "user":
return {"role": "user", "content": content}
return {"role": "assistant", "content": content}


def convert_prompt_message_to_message_param(
prompt_message: PromptMessage,
) -> MessageParam:
Expand All @@ -104,7 +112,7 @@ def convert_prompt_message_to_message_param(
if isinstance(content, dict)
else getattr(content, "text", "")
)
return {"role": role, "content": content_text}
return _message_param(role, content_text)

if isinstance(content, list):
text_blocks = []
Expand All @@ -125,9 +133,9 @@ def convert_prompt_message_to_message_param(
text_blocks.append({"type": "text", "text": item_text})

if text_blocks:
return {"role": role, "content": text_blocks}
return _message_param(role, text_blocks)

return {"role": role, "content": ""}
return _message_param(role, "")


def convert_prompt_messages_to_message_params(
Expand Down
9 changes: 9 additions & 0 deletions mcp/tests/test_claude.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,15 @@ def test_chat_omits_optional_parameters_by_default(claude):
assert "thinking" not in params


def test_chat_sends_no_sampling_parameters(claude):
"""API version 2026-10-21, which anthropic 2.x sends, rejects a request that
carries temperature, top_p or top_k with a 400, even at the default value."""
claude.chat(messages=[], system="be brief", tools=[{"name": "read"}], thinking=True)

params = claude.client.messages.calls[0]
assert not {"temperature", "top_p", "top_k"} & params.keys()


def test_chat_includes_optional_parameters_when_asked(claude):
claude.chat(messages=[], system="be brief", tools=[{"name": "read"}], thinking=True)

Expand Down
Loading
Loading