mirror of
https://github.com/open-multi-agent/open-multi-agent.git
synced 2026-10-19 01:31:07 -05:00
* refactor(repo): monorepo groundwork for an easier first run Trying OMA for the first time costs more than it should: a lot to read before anything runs, and no one-command way to watch a team actually work. The plan is to fix that with a `npm create oma` scaffold (a multi-agent demo that runs on the first try) and an npm README that leads with getting started. Neither lands cleanly until core lives in its own package, so this PR does that part first. This is groundwork, not the payoff: nothing changes for anyone installing `@open-multi-agent/core` today. It turns the repo into an npm-workspaces monorepo with core in `packages/core/`, and splits the README into a GitHub landing page (root) and the npm package page (`packages/core/`). Doing it now, while the repo is small, avoids migrating twice. The published package is byte-identical: same name, same three runtime dependencies, same entry points, guarded by an isolated-install smoke test. `npm ci` + lint + test (1060) + build pass; the tarball still ships only `dist/`, `README.md`, `LICENSE`, and `package.json`. * build: restore cross-platform optional deps in package-lock.json Regenerating the lockfile on macOS pruned the non-host rollup/esbuild optional binaries (npm/cli#4828), so Linux CI could not load @rollup/rollup-linux-x64-gnu and vitest failed to start. Rebuild the lock from the pre-migration base so every platform variant is present again. Verified with npm ci + lint + build + test (1060). * fix(examples,docs): correct monorepo paths and anchors after the packages/core move with-vercel-ai-sdk example (pre-existing, surfaced by the move): - package.json: the @open-multi-agent/core file: link was file:../../, which resolves to packages/core/examples (no manifest). Corrected to file:../../.. (packages/core); predev cd updated to match. - package-lock.json regenerated: was linking ../.. and pinning a stale core 1.4.0; now links ../../.. at 1.7.0. - README: setup paths and the file: note updated. docs missed by the move: - docs/cli.md: repo-clone build output is now packages/core/dist/cli/oma.js; the quick-start anchor now lives in the package README. - CONTRIBUTING.md: the architecture anchor and src/ entry-point paths now live under packages/core/.
94 lines
4.3 KiB
TypeScript
94 lines
4.3 KiB
TypeScript
/**
|
|
* Cross-provider reasoning preservation via `preserveReasoningAsText`
|
|
*
|
|
* When an `Agent.prompt()` conversation runs against a "reasoning" model that
|
|
* emits `reasoning_content` (OpenAI o-series, DeepSeek reasoner, Anthropic
|
|
* extended-thinking-via-Bedrock, etc.), the model's intermediate thought
|
|
* stream is normally either:
|
|
*
|
|
* (a) native-echoed back when the next turn targets the same provider with
|
|
* a valid signature (Anthropic, Gemini own-issued path), OR
|
|
* (b) silently dropped on the next outbound conversion (everyone else).
|
|
*
|
|
* Opt into {@link AgentConfig.preserveReasoningAsText} to replace (b) with a
|
|
* downgrade — the prior reasoning is wrapped in `<thinking>...</thinking>`
|
|
* text and prepended to the next assistant message, so the receiving model
|
|
* has full context of what was previously considered. See #223 for design.
|
|
*
|
|
* Run:
|
|
* npx tsx examples/patterns/cross-provider-reasoning.ts
|
|
*
|
|
* Prerequisites:
|
|
* ANTHROPIC_API_KEY
|
|
* OPENAI_API_KEY (uses an o-series model for the reasoning emit)
|
|
*/
|
|
|
|
import { Agent } from '../../src/index.js'
|
|
|
|
// --- Caveat: loop-detector interaction --------------------------------------
|
|
//
|
|
// Some OpenAI-compatible local models (and a few hosted ones at low quality
|
|
// tiers) re-emit `<thinking>...</thinking>` text back into their assistant
|
|
// response as if the tag were an instruction template. When this happens
|
|
// across multiple turns, the framework's loop-detector may flag the agent as
|
|
// stuck because the assistant text matches turn-over-turn.
|
|
//
|
|
// Mitigation if you hit this:
|
|
// - Set `loopDetection: { enabled: false }` on the AgentConfig (loses the
|
|
// safety net for other genuine loop cases — use as a debug measure only).
|
|
// - Switch to a model that ignores `<thinking>` text on input (most o-series
|
|
// and Claude models do; problem is mostly with smaller local models).
|
|
// - Disable `preserveReasoningAsText` entirely; accept reasoning loss.
|
|
//
|
|
// The example below does NOT trigger the issue against gpt-5 / Claude, but is
|
|
// included so contributors testing local models know what to watch for.
|
|
// ----------------------------------------------------------------------------
|
|
|
|
async function main(): Promise<void> {
|
|
if (!process.env['ANTHROPIC_API_KEY'] || !process.env['OPENAI_API_KEY']) {
|
|
console.log('Skip: ANTHROPIC_API_KEY and OPENAI_API_KEY both required.')
|
|
process.exit(0)
|
|
}
|
|
|
|
// Step 1: a reasoning-capable OpenAI model produces a turn that includes
|
|
// a `reasoning_content` chunk. The adapter extracts it into a ReasoningBlock
|
|
// with `provenance: 'openai'`, stored in the agent's persistent history.
|
|
const agent = new Agent({
|
|
name: 'cross-provider-demo',
|
|
model: 'gpt-5', // o-series-style reasoning model
|
|
provider: 'openai',
|
|
systemPrompt: 'You reason carefully then give a short final answer.',
|
|
preserveReasoningAsText: true,
|
|
// compressReasoningText defaults to `true` whenever preserve is true,
|
|
// so long reasoning chains are head+tail truncated to 1200 chars by
|
|
// default. Override with `{ minChars: N }` to tune, or `false` to disable
|
|
// (footgun — long CoT will eat your prompt budget).
|
|
compressReasoningText: { minChars: 1500 },
|
|
})
|
|
|
|
const firstAnswer = await agent.prompt(
|
|
'A train leaves Boston at 60 mph heading west. Another leaves NYC at 80 mph heading north. ' +
|
|
'After 2 hours, what is the straight-line distance between them? Show your reasoning briefly.',
|
|
)
|
|
console.log('--- Turn 1 (OpenAI o-series) ---')
|
|
console.log(firstAnswer.output.slice(0, 500))
|
|
|
|
// Step 2: swap the model to Anthropic mid-conversation. Without
|
|
// `preserveReasoningAsText`, the prior reasoning would be silently dropped
|
|
// because Anthropic's outbound only round-trips its own-signed thinking
|
|
// blocks. With opt-in on, the prior OpenAI reasoning is replayed as
|
|
// `<thinking>` text so Claude sees the chain.
|
|
//
|
|
// (Note: in real code you'd construct a new Agent or use a per-call model
|
|
// override. The Agent instance owns its adapter; this example keeps things
|
|
// short and just illustrates the persistence model.)
|
|
console.log('\n(Hypothetical: if you constructed a second Agent against ' +
|
|
'Anthropic and replayed the same messageHistory, the prior OpenAI ' +
|
|
'reasoning would arrive as `<thinking>` text on its first request.)')
|
|
}
|
|
|
|
void main().catch((err: unknown) => {
|
|
console.error(err)
|
|
process.exit(1)
|
|
})
|