Skip to content

chore: change prompt content column, close #1553#1580

Merged
looplj merged 1 commit into
unstablefrom
dev-tmp
May 1, 2026
Merged

chore: change prompt content column, close #1553#1580
looplj merged 1 commit into
unstablefrom
dev-tmp

Conversation

@looplj

@looplj looplj commented May 1, 2026

Copy link
Copy Markdown
Owner

No description provided.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request implements support for the legacy OpenAI completions API, adding new routes, model definitions, and specialized transformers. A significant architectural change updates the Outbound transformer interface to pass the original request to streaming and aggregation methods, enabling better routing for composite transformers. Review feedback points out a typo in the DeepSeek completion URL configuration and identifies logic errors in the completion stream aggregator, which incorrectly merges multiple response choices and fails to return the full set of aggregated choices.

I am having trouble creating individual review comments. Click here to see my feedback.

llm/transformer/deepseek/outbound.go (57)

high

The URL suffix # seems to be a typo for ##. In this repository, ## is used as a convention to indicate a raw URL that should not be further normalized or modified by the base transformer. Additionally, since DeepSeek's beta completion endpoint is /beta/completions, and the CompletionOutboundTransformer appends /completions by default unless RawURL is true, you should use beta/completions## to ensure the correct endpoint is called.

BaseURL:        strings.TrimSuffix(baseURL, "/v1") + "/beta/completions##",

llm/transformer/openai/completion_aggregator.go (52-57)

high

The current aggregation logic incorrectly merges text from all choices into a single string. OpenAI completions can return multiple choices (when n > 1), and their content must be accumulated separately based on their index. Interleaving chunks for different choices is possible in streaming.

for _, choice := range compResp.Choices {
			if _, ok := accumulatedChoices[choice.Index]; !ok {
				accumulatedChoices[choice.Index] = &llm.CompletionChoice{Index: choice.Index}
			}
			accumulatedChoices[choice.Index].Text += choice.Text
			if choice.FinishReason != nil {
				accumulatedChoices[choice.Index].FinishReason = choice.FinishReason
			}
		}

llm/transformer/openai/completion_aggregator.go (82-89)

high

The response should include all aggregated choices, sorted by their index, rather than a single hardcoded choice.

Completion: &llm.CompletionResponse{
			Choices: lo.Map(lo.Values(accumulatedChoices), func(c *llm.CompletionChoice, _ int) llm.CompletionChoice {
				if c.FinishReason == nil {
					c.FinishReason = lo.ToPtr("stop")
				}
				return *c
			}),
		},

@looplj looplj changed the title feat: openai/deepseek completion api, close #1458 chore: change prompt content column, close #1553 May 1, 2026
@looplj looplj merged commit 95136c3 into unstable May 1, 2026
4 checks passed
@greptile-apps

greptile-apps Bot commented May 1, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds an OpenAI-compatible legacy completions API (POST /v1/completions) to AxonHub, with first-class support for DeepSeek channels and opt-in support for any OpenAI-compatible channel via custom endpoint configuration. It introduces new inbound/outbound transformers, a stream aggregator, and an llm.CompletionRequest/Response model layer, while also threading the outbound *httpclient.Request through TransformStream and AggregateStreamChunks across all transformer implementations to enable composite routing (e.g., DeepSeek dispatching between chat and completion sub-transformers).

Confidence Score: 5/5

Safe to merge; DeepSeek completions work correctly and all remaining findings are P2 quality/edge-case items.

All four findings are P2: N>1 aggregation edge case, string-only prompt limitation, TransformError duplication, and missing default endpoint for TypeOpenai. None cause data loss or silent wrong results for the feature's primary target (DeepSeek). The interface change is broad but mechanically consistent across all implementations.

llm/transformer/openai/completion_aggregator.go (N>1 choice handling), internal/server/biz/channel_endpoint.go (openAICompatibleDefaultEndpoints gap)

Important Files Changed

Filename Overview
llm/transformer/openai/completion_inbound.go New inbound transformer for the OpenAI legacy completions format; parses requests, serializes responses, handles streaming and errors. TransformError duplicates inbound.go logic verbatim.
llm/transformer/openai/completion_outbound.go New outbound transformer for forwarding completions requests downstream; URL construction, request/response/stream transformation, and error handling all look correct.
llm/transformer/openai/completion_aggregator.go Aggregates completion stream chunks into a full response; collapses all choices into one for N>1 requests, which is incorrect for multi-choice completions.
llm/transformer/openai/completion.go Defines CompletionRequest/Response types; prompt field is string-only (OpenAI spec allows array), which will cause a 400 for array-prompt clients.
llm/transformer/openai/error.go New shared error-parsing helper that extracts OpenAI error structure from HTTP responses; clean extraction from the existing outbound transformer.
llm/transformer/deepseek/outbound.go Composite transformer now routes RequestTypeCompletion to a dedicated CompletionOutboundTransformer pointing at DeepSeek's /beta/completions; URL construction using # suffix is handled correctly by NormalizeBaseURL.
internal/server/biz/channel_endpoint.go Adds openai/completions to SupportedAPIFormats and as a default endpoint for TypeDeepseek only; openAICompatibleDefaultEndpoints (TypeOpenai, TypeVercel, etc.) is not updated, causing confusing errors for those channel types.
internal/server/orchestrator/custom_endpoints.go Adds completionCapableAPIFormats map and routes RequestTypeCompletion through it; consistent with existing pattern for other request types.
llm/transformer/interfaces.go Interface updated to pass *httpclient.Request to TransformStream and AggregateStreamChunks, enabling composite transformers (e.g., DeepSeek) to route by request type; all implementors updated consistently.
internal/server/api/openai.go Wires up a new CompletionHandlers using the CompletionInboundTransformer and exposes a Completion handler method; structure mirrors the existing ChatCompletion handler correctly.
internal/server/routes.go Registers POST /v1/completions route pointing to the new Completion handler; single-line change, correct placement.
frontend/src/features/requests/utils/response-parser.ts Adds parsing for legacy completions format (choices[].text) in both non-streaming and streaming paths; straightforward and correct.
llm/completion.go New llm-layer types for CompletionRequest, CompletionResponse, and CompletionChoice; clean modeling consistent with other request types in the package.
llm/pipeline/empty_response.go Adds hasResponseContent check for completion choices (choice.Text != ""), preventing false empty-response detection for completions streams.

Sequence Diagram

sequenceDiagram
    participant Client
    participant Router as POST /v1/completions
    participant InboundT as CompletionInboundTransformer
    participant Orch as Orchestrator
    participant DS as DeepSeek OutboundTransformer
    participant CompT as CompletionOutboundTransformer
    participant Provider as DeepSeek API /beta/completions

    Client->>Router: POST /v1/completions {model, prompt, ...}
    Router->>InboundT: TransformRequest(httpReq)
    InboundT-->>Orch: llm.Request{RequestType=completion, Completion={...}}
    Orch->>DS: TransformRequest(llmReq)
    DS->>CompT: route via RequestTypeCompletion
    CompT-->>Orch: httpclient.Request{URL=/beta/completions}
    Orch->>Provider: POST /beta/completions
    Provider-->>Orch: CompletionResponse (or stream)
    alt Non-streaming
        Orch->>DS: TransformResponse(httpResp)
        DS->>CompT: route via httpResp.Request.RequestType
        CompT-->>Orch: llm.Response{Completion={...}}
        Orch->>InboundT: TransformResponse(llmResp)
        InboundT-->>Client: CompletionResponse JSON
    else Streaming
        Orch->>DS: TransformStream(req, stream)
        DS->>CompT: route via req.RequestType
        CompT-->>Orch: Stream[llm.Response]
        Orch->>InboundT: TransformStream(llmStream)
        InboundT-->>Client: SSE stream of CompletionResponse chunks
    end
Loading

Comments Outside Diff (4)

  1. llm/transformer/openai/completion_aggregator.go, line 51-59 (link)

    P2 N>1 choices collapsed into a single choice during aggregation

    When n > 1 is requested, each streaming chunk can contain multiple choices, but the aggregator concatenates all of their .Text values into a single string and emits one choice at index 0. The caller receives a single-choice response regardless of the n parameter, losing all choices beyond the first.

    A correct aggregator should track a map[int]string keyed by choice index and emit one CompletionChoice per entry, mirroring the pattern used in the chat-completion aggregator.

  2. llm/transformer/openai/completion.go, line 7 (link)

    P2 prompt is string-only; OpenAI spec permits an array

    The OpenAI Completions API defines prompt as string | array<string | array<int>>. The current string-only field means a client sending "prompt": ["hello", "world"] will hit a JSON unmarshal error and receive a confusing 400 "failed to decode completion request", rather than a clear "unsupported prompt format" message.

    Consider using a custom type (similar to the existing Stop type) that accepts both a single string and an array, or at minimum document the limitation in a comment so callers know only the string form is accepted.

  3. llm/transformer/openai/completion_inbound.go, line 226-292 (link)

    P2 TransformError is a verbatim copy of InboundTransformer.TransformError

    The entire TransformError implementation here (lines 226–292) is identical to the one in inbound.go. Any future change to error-handling logic would need to be applied in two places. Consider extracting a shared TransformOpenAIInboundError helper (analogous to the new TransformOpenAIError in error.go) and having both inbound transformers delegate to it.

  4. internal/server/biz/channel_endpoint.go, line 65-72 (link)

    P2 openai/completions is not included in openAICompatibleDefaultEndpoints

    openAICompatibleDefaultEndpoints is shared by TypeOpenai, TypeVercel, TypeDeepinfra, TypePpio, and TypeSiliconflow. None of them will have the openai/completions endpoint by default, so a POST /v1/completions to a channel backed by any of these types falls through SelectAPIFormatForRequestType's fallback to openai/chat_completions. The OpenAI outbound transformer then rejects the request with "messages are required" — a misleading error for a caller making a valid completion request.

    If completions support is intentionally opt-in (custom-endpoint only) for these providers, a comment here explaining that decision would help future maintainers avoid confusion.

Reviews (1): Last reviewed commit: "chore: change prompt content column, clo..." | Re-trigger Greptile

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant