Skip to content

feat(sdk): support custom title generation prompts - #4564

Open
DevinVinson wants to merge 7 commits into
mainfrom
dv/custom-title-generation-prompt
Open

feat(sdk): support custom title generation prompts#4564
DevinVinson wants to merge 7 commits into
mainfrom
dv/custom-title-generation-prompt

Conversation

@DevinVinson

@DevinVinson DevinVinson commented Aug 20, 2026

Copy link
Copy Markdown
Member

HUMAN: Will need some 👀 to check but hopefully this is the right direction.


AGENT:

Implemented and exercised the complete Agent Server path: request validation and
conversation metadata persistence, AutoTitleSubscriber forwarding, prompt rendering,
and the LLM completion message. A live provider-backed run was not performed because
this environment has no LLM credentials; the PR remains a draft for human E2E testing.

Why

Users can choose a title LLM profile, but cannot customize the prompt used to generate
automatic conversation titles. This adds the Agent Server contract required by
#4561 and OpenHands/OpenHands#16761.

Summary

  • Add an optional, persisted title_generation_prompt conversation setting with a 2,000-character limit.
  • Render {conversation_content} and {max_length} placeholders while safely appending conversation content when its placeholder is omitted.
  • Preserve byte-for-byte default prompt behavior for unset or blank overrides and cover request-to-LLM forwarding and metadata round trips.

REST API contract changes

Compared with base OpenAPI 1de2e6d1bfcf for public /api/** paths.

--- base public OpenAPI
+++ head public OpenAPI
@@ -2306,0 +2307 @@
+schema StartConversationRequest property prompt optional schema=anyOf=[type="string" maxLength=2000,type="null"]

Issue Number

Closes #4561

How to Test

  1. Run uv run pytest tests/sdk/conversation/test_generate_title.py tests/agent_server/test_conversation_service.py tests/agent_server/test_event_service.py.
  2. Confirm all 237 tests pass.
  3. Start a conversation with autotitle: true and a prompt such as Write a title under {max_length} characters for: {conversation_content}; after the first user message, confirm the completion receives the rendered custom user prompt and meta.json retains the template.

Validation performed:

  • make build
  • Pre-commit hooks on every edited file
  • Focused suite above: 237 passed
  • Commit hooks: Ruff format/lint, pycodestyle, Pyright, import rules, and registration checks passed

Video/Screenshots

Not applicable to this backend-only change. Live LLM E2E evidence is pending human testing.

Design Doc

Not included; the API behavior and fallback rules are documented in the companion docs PR linked below.

Type

  • Bug fix
  • Feature
  • Refactor
  • Breaking change
  • Docs / chore

Notes


🐳 Agent Server images for this PR — GHCR package, pull/run commands, and all pushed tags (click to expand)

GHCR package: https://github.com/OpenHands/agent-sdk/pkgs/container/agent-server

Variants & Base Images

Variant Architectures Base Image Docs / Tags
java amd64, arm64 eclipse-temurin:17-jdk Link
python amd64, arm64 nikolaik/python-nodejs:python3.13-nodejs22-slim Link
golang amd64, arm64 golang:1.21-bookworm Link

Pull (multi-arch manifest)

# Each variant is a multi-arch manifest supporting both amd64 and arm64
docker pull ghcr.io/openhands/agent-server:4afe9a9-python

Run

docker run -it --rm \
  -p 8000:8000 \
  --name agent-server-4afe9a9-python \
  ghcr.io/openhands/agent-server:4afe9a9-python

All tags pushed for this build

ghcr.io/openhands/agent-server:4afe9a9-golang-amd64
ghcr.io/openhands/agent-server:4afe9a979fda23b125388e898d8c3f7895f2696e-golang-amd64
ghcr.io/openhands/agent-server:dv-custom-title-generation-prompt-golang-amd64
ghcr.io/openhands/agent-server:4afe9a9-golang_tag_1.21-bookworm-amd64
ghcr.io/openhands/agent-server:4afe9a9-golang-arm64
ghcr.io/openhands/agent-server:4afe9a979fda23b125388e898d8c3f7895f2696e-golang-arm64
ghcr.io/openhands/agent-server:dv-custom-title-generation-prompt-golang-arm64
ghcr.io/openhands/agent-server:4afe9a9-golang_tag_1.21-bookworm-arm64
ghcr.io/openhands/agent-server:4afe9a9-java-amd64
ghcr.io/openhands/agent-server:4afe9a979fda23b125388e898d8c3f7895f2696e-java-amd64
ghcr.io/openhands/agent-server:dv-custom-title-generation-prompt-java-amd64
ghcr.io/openhands/agent-server:4afe9a9-eclipse-temurin_tag_17-jdk-amd64
ghcr.io/openhands/agent-server:4afe9a9-java-arm64
ghcr.io/openhands/agent-server:4afe9a979fda23b125388e898d8c3f7895f2696e-java-arm64
ghcr.io/openhands/agent-server:dv-custom-title-generation-prompt-java-arm64
ghcr.io/openhands/agent-server:4afe9a9-eclipse-temurin_tag_17-jdk-arm64
ghcr.io/openhands/agent-server:4afe9a9-python-amd64
ghcr.io/openhands/agent-server:4afe9a979fda23b125388e898d8c3f7895f2696e-python-amd64
ghcr.io/openhands/agent-server:dv-custom-title-generation-prompt-python-amd64
ghcr.io/openhands/agent-server:4afe9a9-nikolaik_s_python-nodejs_tag_python3.13-nodejs22-slim-amd64
ghcr.io/openhands/agent-server:4afe9a9-python-arm64
ghcr.io/openhands/agent-server:4afe9a979fda23b125388e898d8c3f7895f2696e-python-arm64
ghcr.io/openhands/agent-server:dv-custom-title-generation-prompt-python-arm64
ghcr.io/openhands/agent-server:4afe9a9-nikolaik_s_python-nodejs_tag_python3.13-nodejs22-slim-arm64
ghcr.io/openhands/agent-server:4afe9a9-golang
ghcr.io/openhands/agent-server:4afe9a979fda23b125388e898d8c3f7895f2696e-golang
ghcr.io/openhands/agent-server:dv-custom-title-generation-prompt-golang
ghcr.io/openhands/agent-server:4afe9a9-golang_tag_1.21-bookworm
ghcr.io/openhands/agent-server:4afe9a9-java
ghcr.io/openhands/agent-server:4afe9a979fda23b125388e898d8c3f7895f2696e-java
ghcr.io/openhands/agent-server:dv-custom-title-generation-prompt-java
ghcr.io/openhands/agent-server:4afe9a9-eclipse-temurin_tag_17-jdk
ghcr.io/openhands/agent-server:4afe9a9-python
ghcr.io/openhands/agent-server:4afe9a979fda23b125388e898d8c3f7895f2696e-python
ghcr.io/openhands/agent-server:dv-custom-title-generation-prompt-python
ghcr.io/openhands/agent-server:4afe9a9-nikolaik_s_python-nodejs_tag_python3.13-nodejs22-slim

About Multi-Architecture Support

  • Each variant tag (e.g., 4afe9a9-python) is a multi-arch manifest supporting both amd64 and arm64
  • Docker automatically pulls the correct architecture for your platform
  • Individual architecture tags (e.g., 4afe9a9-python-amd64) are also available if needed

Co-authored-by: openhands <openhands@all-hands.dev>
@github-actions

github-actions Bot commented Aug 20, 2026

Copy link
Copy Markdown
Contributor
  ✅ **PR Artifacts Cleaned Up**

  The `.pr/` directory is no longer present.

@github-actions

github-actions Bot commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Python API breakage checks — ✅ PASSED

Result:PASSED

Action log

@github-actions

github-actions Bot commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

REST API breakage checks (OpenAPI) — ✅ PASSED

Result:PASSED

Action log

@DevinVinson DevinVinson added integration-test Runs the integration tests and comments the results release-note-required PR requires explicit release-note coverage for behavioral or default changes labels Aug 20, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Hi! I started running the integration tests on your PR. You will receive a comment with the results shortly.

@github-actions

github-actions Bot commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Coverage

Coverage Report •
FileStmtsMissCoverMissing
openhands-agent-server/openhands/agent_server
   conversation_service.py114612689%181–182, 191, 218–219, 223–224, 229, 332–333, 336–337, 349–350, 364, 537–538, 599, 663, 685, 692–693, 776, 852, 903–904, 911, 943–944, 960, 991, 995, 1012, 1024–1027, 1033–1034, 1043, 1045, 1100, 1106–1107, 1111–1112, 1120, 1147, 1153, 1247, 1253, 1258, 1264, 1272–1273, 1282–1285, 1294, 1306, 1314, 1343, 1349–1350, 1353–1355, 1382, 1434, 1527–1528, 1599, 1654–1656, 1658–1659, 1662–1663, 1683, 1769–1770, 1801–1803, 1806–1807, 1811–1813, 1816–1817, 1821–1823, 1826–1827, 1856, 1865, 1908, 1918–1920, 1980, 1983, 2010, 2020, 2025–2028, 2042, 2053, 2065–2066, 2098, 2192, 2249, 2307, 2322–2323, 2463, 2709, 2762, 2765
openhands-sdk/openhands/sdk/conversation
   request.py931089%75, 334, 340, 346, 348–349, 352–353, 364, 373
   title_utils.py69297%39, 92
TOTAL41392757582% 

@github-actions

Copy link
Copy Markdown
Contributor

🧪 Integration Tests Results

Overall Success Rate: 97.7%
Total Cost: $1.87
Models Tested: 5
Timestamp: 2026-08-20 23:36:56 UTC

📁 Detailed Logs & Artifacts

Click the links below to access detailed agent/LLM logs showing the complete reasoning process for each model. On the GitHub Actions page, scroll down to the 'Artifacts' section to download the logs.

📊 Summary

Model Overall Tests Passed Skipped Total Cost Tokens
litellm_proxy_deepseek_deepseek_v4_flash 100.0% 8/8 1 9 $0.01 466,614
litellm_proxy_anthropic_claude_sonnet_4_6 88.9% 8/9 0 9 $0.53 361,344
litellm_proxy_gemini_3.1_pro_preview 100.0% 9/9 0 9 $0.50 322,192
litellm_proxy_openai_gpt_5.5 100.0% 9/9 0 9 $0.82 315,239
litellm_proxy_minimax_MiniMax_M2.7 100.0% 8/8 1 9 $0.00 252,113

📋 Detailed Results

litellm_proxy_deepseek_deepseek_v4_flash

  • Success Rate: 100.0% (8/8)
  • Total Cost: $0.01
  • Token Usage: prompt: 461,649, completion: 4,965, cache_read: 395,080, reasoning: 1,378
  • Run Suffix: litellm_proxy_deepseek_deepseek_v4_flash_37495c5_deepseek_v4_flash_run_N9_20260820_233442
  • Skipped Tests: 1

Skipped Tests:

  • t08_image_file_viewing: This test requires a vision-capable LLM model. Please use a model that supports image input.

litellm_proxy_anthropic_claude_sonnet_4_6

  • Success Rate: 88.9% (8/9)
  • Total Cost: $0.53
  • Token Usage: prompt: 356,541, completion: 4,803, cache_read: 253,704, cache_write: 102,784, reasoning: 302
  • Run Suffix: litellm_proxy_anthropic_claude_sonnet_4_6_37495c5_claude_sonnet_4_6_run_N9_20260820_233445

Failed Tests:

  • t02_add_bash_hello: Shell script is not executable (Cost: $0.06)

litellm_proxy_gemini_3.1_pro_preview

  • Success Rate: 100.0% (9/9)
  • Total Cost: $0.50
  • Token Usage: prompt: 317,552, completion: 4,640, cache_read: 108,436, reasoning: 2,684
  • Run Suffix: litellm_proxy_gemini_3.1_pro_preview_37495c5_gemini_3_1_pro_run_N9_20260820_233435

litellm_proxy_openai_gpt_5.5

  • Success Rate: 100.0% (9/9)
  • Total Cost: $0.82
  • Token Usage: prompt: 309,618, completion: 5,621, cache_read: 198,656, reasoning: 1,964
  • Run Suffix: litellm_proxy_openai_gpt_5.5_37495c5_gpt_5_5_run_N9_20260820_233444

litellm_proxy_minimax_MiniMax_M2.7

  • Success Rate: 100.0% (8/8)
  • Total Cost: $0.00
  • Token Usage: prompt: 248,534, completion: 3,579, cache_read: 178,184
  • Run Suffix: litellm_proxy_minimax_MiniMax_M2.7_37495c5_minimax_m2_7_run_N9_20260820_233435
  • Skipped Tests: 1

Skipped Tests:

  • t08_image_file_viewing: This test requires a vision-capable LLM model. Please use a model that supports image input.

@DevinVinson
DevinVinson marked this pull request as ready for review August 21, 2026 10:47
@all-hands-bot

all-hands-bot commented Aug 21, 2026

Copy link
Copy Markdown
Collaborator

🚦 CI is currently failing on this PR's latest commit.

Please fix the failing checks before OpenHands reviews it - this is re-checked automatically once you push a new commit. (A maintainer can also request @all-hands-bot as a reviewer to have it reviewed regardless of CI status.)

This is an automated check - no AI was used to generate this comment.

Comment thread openhands-sdk/openhands/sdk/conversation/title_utils.py Outdated
Comment on lines +99 to +116
if title_generation_prompt and title_generation_prompt.strip():
prompt = title_generation_prompt.strip()
includes_conversation = CONVERSATION_CONTENT_PLACEHOLDER in prompt
user_prompt = prompt.replace(
CONVERSATION_CONTENT_PLACEHOLDER, truncated_message
).replace(MAX_LENGTH_PLACEHOLDER, str(max_length))
if not includes_conversation:
user_prompt = f"{user_prompt}\n\nConversation content:\n{truncated_message}"
else:
user_prompt = (
f"Generate a title (maximum {max_length} characters) "
f"for a conversation that starts with this message:\n\n"
f"{truncated_message}."
"Also make sure to include ONE most relevant emoji at "
"the start of the title."
f" Choose the emoji from this list:{emojis_descriptions} "
)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

template = (title_generation_prompt or "").strip()
if template:
    user_prompt = template.replace(
        CONVERSATION_CONTENT_PLACEHOLDER, truncated_message
    ).replace(MAX_LENGTH_PLACEHOLDER, str(max_length))
    if CONVERSATION_CONTENT_PLACEHOLDER not in template:
        user_prompt += f"\n\nConversation content:\n{truncated_message}"
else:
    ...

{"emoji": "♻️", "name": "refactor", "description": "Code refactoring"},
]

CONVERSATION_CONTENT_PLACEHOLDER = "{conversation_content}"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Final[str] in both from typing.

Comment thread openhands-sdk/openhands/sdk/conversation/title_utils.py Outdated
Comment thread openhands-agent-server/openhands/agent_server/conversation_service.py Outdated
Comment thread openhands-sdk/openhands/sdk/conversation/request.py Outdated
Co-authored-by: Vasco Schiavo <115561717+VascoSch92@users.noreply.github.com>
@github-actions github-actions Bot removed the release-note-required PR requires explicit release-note coverage for behavioral or default changes label Aug 21, 2026
DevinVinson and others added 4 commits August 21, 2026 09:08
Co-authored-by: Vasco Schiavo <115561717+VascoSch92@users.noreply.github.com>
…vice.py

Co-authored-by: Vasco Schiavo <115561717+VascoSch92@users.noreply.github.com>
Co-authored-by: Vasco Schiavo <115561717+VascoSch92@users.noreply.github.com>
Co-authored-by: openhands <openhands@all-hands.dev>
@DevinVinson

Copy link
Copy Markdown
Member Author

Thank you @VascoSch92 ! I've updated to prompt and hopefully addressed each of the areas you've pointed out

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

integration-test Runs the integration tests and comments the results

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature: Allow custom title generation prompt via ConversationConfig

3 participants