Skip to content

Advance remove-recursive-submodules to the response_messages fix - #56

Closed
iskakaushik wants to merge 11 commits into
remove-recursive-submodulesfrom
advance-remove-recursive-submodules
Closed

Advance remove-recursive-submodules to the response_messages fix#56
iskakaushik wants to merge 11 commits into
remove-recursive-submodulesfrom
advance-remove-recursive-submodules

Conversation

@iskakaushik

Copy link
Copy Markdown
Collaborator

Rebuilds the ClickHouse integration branch on current main (through #53) plus the response_messages terminal-reply fix (#55), with the usual flatten commit removing the nested zlib/googletest/clickhouse-cpp gitlinks.

Head commit b30aaef is the intended new submodule pin for ClickHouse/ClickHouse#113959.

iskakaushik added 11 commits May 1, 2026 10:49
#40)

* fix: preserve conversation history across multi-step tool calls

Two bugs caused multi-step tool calling to repeatedly re-issue the same
tool calls and drop the system prompt:

- MultiStepCoordinator::execute_multi_step rebuilt step inputs from
  initial_options + only the most recent step's tool calls/results, so
  earlier turns were lost between iterations. Refactor to mirror the
  Vercel AI SDK pattern (packages/ai/src/generate-text/generate-text.ts):
  immutable initial_messages plus a response_messages accumulator;
  each step's input is the concatenation of the two.

- OpenAIRequestBuilder dropped options.system whenever the caller
  supplied a non-empty messages array (the Chat Completions API has no
  top-level system field; system must be a leading role=system message).
  Anthropic's builder already handled this correctly via request["system"].

Validated by the existing MultiStepDuplicateExecutionTest cases, which
were known-failing per issue #26 and now pass for both providers.
Adds tests/cmake_test_discovery_*.json to .gitignore (build artifact).

* style: wrap long comment line to satisfy clang-format
…#41)

* feat(models): refresh OpenAI / Anthropic model identifiers (May 2026)

OpenAI:
- Replace the gpt-4o / gpt-4 / gpt-3.5 lineup with the GPT-5 family as
  current (gpt-5.4 / -pro / -mini / -nano, gpt-5-mini, gpt-5-nano,
  gpt-4.1 / -mini). Older identifiers retained but marked DEPRECATED.
- Default model -> kGpt54.
- Drop unverified gpt-5.5 / gpt-5.5-pro entries (not in OpenAI's public
  catalog or in the Vercel AI SDK's curated list).

Anthropic:
- Promote claude-opus-4-7 / claude-sonnet-4-6 / claude-opus-4-6 /
  claude-haiku-4-5 to current; default model -> kClaudeSonnet46.
- supported_models() now lists both the friendly aliases (e.g.
  claude-sonnet-4-5) and the dated snapshots (e.g. -20250929) so the
  identifiers exposed by ai::anthropic::models::* validate via
  supports_model().

Tests / examples / mocks / README updated to the new defaults. Fixes
the previously-failing AnthropicIntegrationTest.DefaultModelGeneration
which still asserted against the old claude-sonnet-4-5 default.

* style: apply clang-format
#42)

* feat(langfuse): add Langfuse tracing module for ai::Client agent calls

Adds an opt-in ai::langfuse module that wraps generate_text calls and
emits one trace per logical operation with:
- one Generation observation (model, modelParameters, input messages,
  output text, aggregated usage, finish_reason / step count metadata),
- one Span per tool invocation, parented to the generation, with the
  tool's args and result (or error) recorded as input/output.

The module hooks into the existing on_tool_call_start /
on_tool_call_finish callbacks in GenerateOptions so user-installed
callbacks are preserved (chained). After generate_text returns, the
caller invokes Trace::end() to flush the accumulated batch to
POST /api/public/ingestion via the vendored httplib + Basic auth.

Layout
- include/ai/langfuse.h: public surface (Config, Tracer, Trace,
  generate_text helper).
- src/langfuse/tracer.cpp: HTTP / batching / event-shape implementation.
- examples/langfuse_tracing.cpp: end-to-end demo using OpenAI with
  lookup_user / get_weather tools across multiple steps.
- .env.local.example: credentials template (the populated .env.local
  is gitignored).
- CMakeLists.txt: new ai-sdk-cpp-langfuse target (alias ai::langfuse),
  AI_SDK_HAS_LANGFUSE=1, included in ai::sdk and the install/export
  set. Existing targets unchanged.

Verified end-to-end: example produced trace with the expected nested
generation+tool observations against us.cloud.langfuse.com.

* style: apply clang-format
Langfuse module (`include/ai/langfuse.h`, `src/langfuse/tracer.cpp`):
- Single Trace::to_iso8601(time_point) helper replaces three
  duplicated gmtime_r/snprintf blocks (now_iso8601 plus two inline
  lambdas in finish_generation / build_trace_event).
- Capture timestamp once per record_tool_call_{start,finish} event
  (previously two clock reads per span event).
- Anonymous-namespace constants for event type discriminators
  (trace-create / span-create / span-update / generation-create) and
  level / unit strings, replacing scattered magic strings.
- Extract make_span_create + wrap_event helpers; the orphan-span
  fallback in record_tool_call_finish reuses them instead of inlining
  20+ lines of duplicated body construction.
- Drop the over-engineered ParsedHost/parse_host: httplib::Client's
  URL constructor handles scheme/host/port already; we only need a
  base-path extractor for Langfuse instances served under a sub-path.
- Cache httplib::Client + Basic-auth headers on the Tracer (lazily
  constructed under mu_) so repeat send_batch calls reuse the
  connection / TLS session instead of paying a fresh handshake.
- Replace 6-arg `start_trace(name, input, user, session, metadata,
  tags)` sprawl with `start_trace(name, TraceOptions{})` to match the
  rest of the SDK's struct-of-options style.
- Default Config::error_policy = ErrorPolicy::kStrict (was
  best_effort=true) so misconfigurations surface at integration time
  instead of being silently swallowed.
- Reject post-end mutations: set_*/instrument/finish_generation/record_*
  bail out if Trace::end() has fired.
- Replace hand-rolled UUID v4 with stduuid (vendored under
  third_party/stduuid-header-only/).

Multi-step coordinator (`src/tools/multi_step_coordinator.cpp`):
- Avoid re-copying initial_messages every loop iteration. step_messages
  is grown in place: erase back to the immutable prefix + insert the
  running response_messages accumulator, instead of `step_options =
  initial_options; step_options.messages = initial_messages;
  step_options.messages.insert(...)` per step.
- Remove the dead create_next_step_options stub from header + impl
  (not part of any external ABI; was only kept as scaffolding during
  the refactor).
- Trim narrating comment that referenced an external repo path.

Vendoring:
- Add third_party/stduuid-header-only/ (uuid.h + LICENSE) and
  third_party/stduuid-cmake/ wrapper exposing a stduuid::stduuid
  INTERFACE target. Wired into ai-sdk-cpp-langfuse via PRIVATE link.

Verified: full ctest suite (221/227 pass; 6 failing are
ClickHouseIntegrationTest cases that need a local ClickHouse server,
unchanged from main). End-to-end Langfuse example produces the
expected trace with 1 generation + N tool spans nested correctly.
The installed package was unusable from a downstream CMake project:
targets were exported under their internal ai-sdk-cpp-* names while
the config file referenced ai::core and friends, the langfuse
component was missing from the component list, and the OpenSSL,
Threads, and ZLIB dependencies of the static libraries were neither
linked for install-tree consumers nor resolved via find_dependency().

Export every target under its ai:: alias name, declare the
dependencies on the install interface, and find them in the package
config. The umbrella ai::sdk target is now added only when the
consumer requested no explicit components.

Switch httplib compression from brotli to zlib and drop the brotli
submodule; zlib was already a dependency and nothing needed brotli.
Mark the zlib and googletest subprojects EXCLUDE_FROM_ALL so their
targets stop polluting the default build.

Also fix the developer scripts: the format and lint sweeps now
exclude only build-* directories instead of any matching path
component (a file named build-info.cpp would have been skipped
silently), clang-tidy receives the compile database's directory as
-p expects, and build.py prints the debug-suffixed example paths the
examples build actually produces. Add ANTHROPIC_API_KEY to
.env.local.example.
std::atomic<std::shared_ptr> in logger.h does not compile on
standard libraries that have not implemented P0718 (notably libc++,
which this project selects on Apple), so any macOS build of the SDK
or of downstream code including <ai/logger.h> failed. Feature-test
__cpp_lib_atomic_shared_ptr, fall back to the atomic_load and
atomic_store free functions elsewhere, and document the lifetime of
the reference logger() returns.

Treat a set-but-empty OPENAI_API_KEY or ANTHROPIC_API_KEY as absent
through a shared utils::non_empty_env() helper; an empty value
previously produced a client that failed only at request time.

Also size the langfuse ISO-8601 buffer for snprintf's worst case,
drop a std::move on nlohmann json subscripts that suppressed no
copy, silence unused-parameter warnings in the tool factories and
the base stream_text stub, and fix the stale designated-initializer
example in core.h that no longer compiles.
The model constants predate the current provider lineups: the
GPT-5.6 family and GPT-5.5 were missing, as were Claude Fable 5,
Opus 5, Opus 4.8, and Sonnet 5, while long-retired identifiers
(GPT-4o, GPT-4, GPT-3.5, the o-series, Claude Sonnet/Opus 4.0) were
still advertised. Add the current identifiers with explicit dated
snapshot constants, remove the retired ones, and move the defaults
to gpt-5.6 and claude-sonnet-5. Removing the retired constants is a
source-compatibility change for callers still referencing them.

Two request-building accommodations come with the new models.
GPT-5.6 defaults to reasoning on Chat Completions, which does not
support function tools and can consume the whole completion budget
without producing user-visible text, so requests pin
reasoning_effort to "none". Recent Claude models reject sampling
controls, so temperature and top_p are omitted for those model IDs
with a warning log.

Update supported_models(), mocks, fixtures, unit and integration
tests, examples, and documentation accordingly. Tests that need a
neutral model now use the literal "test-model" instead of a real
identifier.
The OpenAI and Anthropic stream implementations had drifted into
near-identical copies of the same threading, queueing, and SSE
parsing code, and both mishandled several streaming cases: tool-call
deltas were never assembled into events, finish events carried no
usage or finish reason, API error bodies on non-200 responses were
lost, and a stream could emit several terminal events.

Introduce providers::streaming::HttpSseStream, which owns the stream
thread, event queue, HTTP request and SSE line splitting, pending
tool-call assembly, and emit-once finish semantics. Each provider
implementation now only translates its SSE lines into stream events.

Behavioral fixes that come with the rework:

- Streamed tool calls are accumulated per index and emitted as
  tool-call events, including when a gateway closes the stream
  without [DONE] or content_block_stop.
- Exactly one finish event terminates every stream, carrying usage
  and the real finish reason on success and kFinishReasonError with
  no usage on failure. OpenAI streams now request
  stream_options.include_usage, without which the API omits usage
  from streaming responses entirely.
- Error bodies of non-200 responses are captured via a response
  handler (httplib routes them through the content receiver, leaving
  response.body empty) so auth and rate-limit failures stay
  diagnosable. A user-initiated stop is no longer reported as a
  network error.
- Anthropic SSE parsing tolerates present-but-null JSON fields from
  gateways, keeps a tool_use block's initial input separate from
  streamed input_json_delta fragments, completes on message_stop
  instead of waiting for the connection to close, and no longer
  races or terminates on a second start_stream call.
The OpenAI factory already accepts a retry::RetryConfig so callers
can tune retry behavior for unreliable networks, but the Anthropic
factory offered no equivalent, leaving its clients pinned to the
default policy.

Add the matching create_client(api_key, base_url, retry_config)
overload. The two public AnthropicClient constructors now delegate
to one private constructor taking std::optional<retry::RetryConfig>
rather than duplicating the ProviderConfig block.
GenerateResult::response_messages documents that it includes the
assistant's response so callers can continue the conversation, but
the multi-step coordinator only appended assistant turns on the
tool-call feedback path. A run whose last step finished with stop
(or any non-tool_calls reason) returned the step-1 assistant/tool
exchange without the final answer. This regressed in the multi-step
history fix (f9d8b40) and survived the coordinator refactor
(09c423c); flagged by review on ClickHouse/ClickHouse#113959.

Append the terminal step's assistant text to the accumulator on the
non-tool_calls exits, and cover the contract with unit tests for the
multi-step coordinator (which previously had none).
ClickHouse consumes this repository as a flat submodule and builds it
with its own contrib CMake, so the nested zlib, googletest, and
clickhouse-cpp submodules (used only for this repository's own tests
and the langfuse module) must not appear as gitlinks. Same shape as
the previous heads of this integration branch.
@iskakaushik

Copy link
Copy Markdown
Collaborator Author

Superseded by #57: with the nested submodules replaced by pinned FetchContent, ClickHouse can pin main directly and the remove-recursive-submodules integration branch is no longer needed.

@iskakaushik
iskakaushik deleted the advance-remove-recursive-submodules branch August 19, 2026 18:17
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