# API — Bedrock LLM Calls

dpg has no external HTTP API of its own. "API" in this context means every call made **into AWS Bedrock**. This document traces each one with full input/output detail.

There are two Bedrock clients:

- **`BedrockLLMClient`** (`shared/bedrock_raw_client.py`) — direct `boto3.client("bedrock-runtime").invoke_model()` call. Used by step-02 and step-03.
- **`ChatBedrockConverse`** (`shared/bedrock_client.py`) — LangChain wrapper over the Bedrock Converse API. Used by step-04 and step-05.

---

## CALL 1 — PRD Generation (Step 02a)

```
┌──────────────────────────────────────────────────────────────────┐
│ CALL: BedrockLLMClient.generate() — PRD from prompt              │
│ FILE: pipeline/step-02-prd-generation/pipeline/prd_generator.py  │
│       _llm_generate_prd()                                        │
├──────────────────────────────────────────────────────────────────┤
│ TRIGGERED BY                                                      │
│   file:    prd_generator.py                                       │
│   fn:      generate_from_prompt()                                 │
│   called from: orchestrator.py step-02 block                     │
├──────────────────────────────────────────────────────────────────┤
│ REQUEST                                                           │
│   model:   BACKEND_MODEL_ID (default: claude-3-5-sonnet-20241022)│
│   body: {                                                         │
│     "anthropic_version": "bedrock-2023-05-31",                   │
│     "max_tokens": 32768,                                          │
│     "temperature": 0.0,                                           │
│     "messages": [{                                                │
│       "role": "user",                                             │
│       "content": "<PRD generation prompt with user_prompt>"       │
│     }]                                                            │
│   }                                                               │
│   images:  None (text-only call)                                  │
├──────────────────────────────────────────────────────────────────┤
│ SUCCESS RESPONSE                                                  │
│   status:  200                                                    │
│   body:    response_body["content"][0]["text"]                    │
│   type:    str — markdown PRD document                            │
│   goes to: _llm_generate_prd() returns str                       │
│            → generate_from_prompt() stores as prd_text            │
│            → prd_file.write_text(prd_text)                        │
│            → LoadedPRD(path=prd_file, text=prd_text, ...)         │
├──────────────────────────────────────────────────────────────────┤
│ ERROR RESPONSE                                                    │
│   throws:  boto3 ClientError / ThrottlingException               │
│   caught:  not caught — propagates to RunLog.step() context      │
│   shown:   StepEvent.status="failed", error=repr(exc)            │
│            printed to stderr by main.py                           │
└──────────────────────────────────────────────────────────────────┘
```

---

## CALL 2 — DDL Generation (Step 02b)

```
┌──────────────────────────────────────────────────────────────────┐
│ CALL: BedrockLLMClient.generate() — DDL from PRD                 │
│ FILE: pipeline/step-02-prd-generation/pipeline/prd_generator.py  │
│       _llm_generate_ddl()                                        │
├──────────────────────────────────────────────────────────────────┤
│ TRIGGERED BY                                                      │
│   file:    prd_generator.py                                       │
│   fn:      generate_from_prompt()                                 │
│   after:   CALL 1 completes and prd_text is available            │
├──────────────────────────────────────────────────────────────────┤
│ REQUEST                                                           │
│   model:   BACKEND_MODEL_ID (claude-3-5-sonnet-20241022)         │
│   body: {                                                         │
│     "anthropic_version": "bedrock-2023-05-31",                   │
│     "max_tokens": 32768,                                          │
│     "temperature": 0.0,                                           │
│     "messages": [{                                                │
│       "role": "user",                                             │
│       "content": "<DDL prompt with prd_text[:6000] + user_prompt>"│
│     }]                                                            │
│   }                                                               │
│   note:   PRD text is truncated to first 6000 chars in prompt    │
├──────────────────────────────────────────────────────────────────┤
│ SUCCESS RESPONSE                                                  │
│   status:  200                                                    │
│   body:    response_body["content"][0]["text"]                    │
│   type:    str — Postgres SQL DDL (CREATE TABLE statements)       │
│   goes to: ddl_file.write_text(ddl_text)                         │
│            → LoadedPRD.ddl_path = ddl_file                       │
│            → orchestrator reads ddl_path for step-03             │
├──────────────────────────────────────────────────────────────────┤
│ ERROR RESPONSE                                                    │
│   throws:  boto3 ClientError                                      │
│   caught:  propagates to RunLog.step("step-02-prd-generation")   │
│   shown:   run_log.md step status = "failed"                     │
└──────────────────────────────────────────────────────────────────┘
```

---

## CALL 3 — Backend Requirement Analysis (Step 03)

```
┌──────────────────────────────────────────────────────────────────┐
│ CALL: BedrockLLMClient.generate() — requirement analysis         │
│ FILE: pipeline/step-03-backend-generation/pipeline/backend_gen/  │
│       requirement_analyzer.py                                    │
├──────────────────────────────────────────────────────────────────┤
│ TRIGGERED BY                                                      │
│   file:    backend_gen/orchestrator.py                            │
│   fn:      Orchestrator.run() step 1/9                           │
│   called from: orchestrator.py step-03 block                     │
├──────────────────────────────────────────────────────────────────┤
│ REQUEST                                                           │
│   model:   BACKEND_MODEL_ID (claude-3-5-sonnet-20241022)         │
│   body:    prompt built from LoadedGenerationInput               │
│            { ddl_text, prd_text, user_prompt, image_files }      │
│   images:  optional — list[{ media_type, data: base64 }]         │
│   max_tokens: 32768                                               │
├──────────────────────────────────────────────────────────────────┤
│ SUCCESS RESPONSE                                                  │
│   type:    str — JSON or structured text                          │
│   goes to: RequirementAnalyzer returns RequirementAnalysis        │
│            → consolidate() normalises names + validates           │
│            → Orchestrator.run() step 2/9                         │
├──────────────────────────────────────────────────────────────────┤
│ ERROR RESPONSE                                                    │
│   throws:  boto3 ClientError or JSON parse error                 │
│   caught:  propagates to Orchestrator.run()                       │
│   shown:   RunLog step "step-03-backend-generation" status=failed │
└──────────────────────────────────────────────────────────────────┘
```

---

## CALL 4 — FastAPI Code Generation (Step 03, multiple calls)

```
┌──────────────────────────────────────────────────────────────────┐
│ CALL: BedrockLLMClient.generate() × N modules — FastAPI code     │
│ FILE: backend_gen/fastapi_code_generator.py                      │
│       FastAPICodeGenerator.generate()                            │
├──────────────────────────────────────────────────────────────────┤
│ TRIGGERED BY                                                      │
│   fn:      Orchestrator.run() step 3/9                           │
│   called once per module in the analysis result                  │
├──────────────────────────────────────────────────────────────────┤
│ REQUEST                                                           │
│   model:   BACKEND_MODEL_ID                                       │
│   body:    prompt with module spec from RequirementAnalysis dict  │
│   images:  None                                                   │
├──────────────────────────────────────────────────────────────────┤
│ SUCCESS RESPONSE                                                  │
│   type:    str — Python source code (router.py, models.py, etc.) │
│   goes to: module_files[filename] = code                         │
│            → generate_manifest_json(module_files)                │
│            → OutputWriter.write_all()                            │
├──────────────────────────────────────────────────────────────────┤
│ ERROR RESPONSE                                                    │
│   throws:  boto3 ClientError                                      │
│   caught:  propagates up through Orchestrator.run()              │
│   shown:   step "step-03" fails; partial files may be on disk    │
└──────────────────────────────────────────────────────────────────┘
```

---

## CALL 5 — Page Detection (Step 04a)

```
┌──────────────────────────────────────────────────────────────────┐
│ CALL: ChatBedrockConverse.invoke() — page detection              │
│ FILE: ir_pipeline/services/page_detection_service.py             │
│       detect_pages()                                             │
├──────────────────────────────────────────────────────────────────┤
│ TRIGGERED BY                                                      │
│   file:    ir_pipeline/services/multi_page_service.py            │
│   fn:      generate_app_ir() — "Step 2 — Detect pages"          │
├──────────────────────────────────────────────────────────────────┤
│ REQUEST                                                           │
│   model:   model_config.detection_model (default: Opus 4.6)      │
│   content: [                                                      │
│     { type: "image", source: { type: "base64", ... } },  ×N     │
│     { type: "text",  text: user_prompt + PRD + API manifest }   │
│   ]                                                               │
│   via:     timed_llm_call() context manager                      │
├──────────────────────────────────────────────────────────────────┤
│ SUCCESS RESPONSE                                                  │
│   type:    str — JSON matching AppPlan schema                     │
│   parsed:  extract_json_object(response_text)                    │
│            → AppPlan.model_validate(json.loads(json_str))        │
│   goes to: generate_app_ir() → app_plan stored in AppIRBundle    │
│   side effect: RunLog.record_llm_call() records token usage      │
├──────────────────────────────────────────────────────────────────┤
│ ERROR RESPONSE                                                    │
│   throws:  botocore exceptions or pydantic ValidationError       │
│   caught:  max_attempts retry within detect_pages()              │
│            → if all retries fail: propagates to multi_page_service│
│   shown:   RunLog step "step-04-ir-generation" status=failed     │
└──────────────────────────────────────────────────────────────────┘
```

---

## CALL 6 — IR Bundle Generation Per Page (Step 04b, ×N pages)

```
┌──────────────────────────────────────────────────────────────────┐
│ CALL: ChatBedrockConverse.invoke() — IR bundle per page          │
│ FILE: ir_pipeline/services/ir_generation_service.py              │
│       generate_ir_bundle_for_page()                              │
├──────────────────────────────────────────────────────────────────┤
│ TRIGGERED BY                                                      │
│   fn:      multi_page_service.generate_app_ir() — page loop      │
│   once per page in app_plan.pages                                │
├──────────────────────────────────────────────────────────────────┤
│ REQUEST                                                           │
│   model:   model_config.ir_model (default: Opus 4.6)             │
│   content: [                                                      │
│     { type: "image", ... } × page_images,                        │
│     { type: "text",  text: IR generation prompt with:            │
│         - page_node metadata                                      │
│         - app_plan context                                        │
│         - api_manifest summary                                    │
│         - prd_text (if available)                                 │
│         - IRBundle schema description                             │
│     }                                                             │
│   ]                                                               │
├──────────────────────────────────────────────────────────────────┤
│ SUCCESS RESPONSE                                                  │
│   type:    str — JSON matching IRBundle 12-section schema         │
│   parsed:  extract_json_object() → IRBundle.model_validate()     │
│   goes to: IRPage(page_node, ir_bundle)                          │
│            → AppIRBundle.ir_pages list                           │
│   written: s04_out/ir/<page_id>_ir.json                         │
│   side effect: RunLog.record_llm_call() × N                      │
├──────────────────────────────────────────────────────────────────┤
│ ERROR RESPONSE                                                    │
│   throws:  pydantic ValidationError (if schema mismatch)         │
│            or boto3 error                                         │
│   caught:  retry logic within ir_generation_service              │
│   shown:   warning logged; partial IRBundle may be used          │
└──────────────────────────────────────────────────────────────────┘
```

---

## CALL 7 — React TSX Generation Per Page (Step 05, ×N pages)

```
┌──────────────────────────────────────────────────────────────────┐
│ CALL: ChatBedrockConverse.invoke() — TSX generation per page     │
│ FILE: react_pipeline/services/react_generation_service.py        │
│       generate_page_react_code()                                 │
├──────────────────────────────────────────────────────────────────┤
│ TRIGGERED BY                                                      │
│   file:    react_gen.py                                           │
│   fn:      generate_react_pages() — page loop                    │
│   once per IRPage in AppIRBundle.ir_pages                        │
├──────────────────────────────────────────────────────────────────┤
│ REQUEST                                                           │
│   model:   model_config.react_model (default: Sonnet 4.5)        │
│   content: text-only (no images in step-05)                      │
│   prompt:  ir_bundle JSON + page_node + app_plan + api_manifest  │
│            + React/Ant Design code generation instructions        │
├──────────────────────────────────────────────────────────────────┤
│ SUCCESS RESPONSE                                                  │
│   type:    str — TypeScript React component (.tsx source)         │
│   parsed:  extract_code_block(response_text)                     │
│   goes to: PageBundle.react_code = tsx_code                      │
│            → MultiPageBundle.pages list                          │
│            → write_generated_files() writes to frontend/src/pages/│
│   side effect: RunLog.record_llm_call()                          │
├──────────────────────────────────────────────────────────────────┤
│ ERROR RESPONSE                                                    │
│   throws:  boto3 ClientError                                      │
│   caught:  propagates to react_gen.generate_react_pages()        │
│   shown:   RunLog step "step-05-react-generation" status=failed  │
└──────────────────────────────────────────────────────────────────┘
```

---

## Token Usage Tracking

All Bedrock calls record token usage via `RunLog.record_llm_call()`. Two paths:

1. **`timed_llm_call()` context manager** (`shared/llm_logging.py`) — used by step-04 and step-05. Extracts usage from `response.response_metadata["usage"]` (Bedrock camelCase) or `response.usage_metadata` (LangChain snake_case).

2. **`BedrockLLMClient.generate()`** (`shared/bedrock_raw_client.py`) — used by step-02 and step-03. Reads `response_body["usage"]` directly from the boto3 JSON response.

Both paths call `get_active().record_llm_call(model, duration_ms, input_tokens, output_tokens)` if a `RunLog` is active.

---

## Related source files

- [shared/bedrock_raw_client.py](../shared/bedrock_raw_client.py) — direct boto3 client
- [shared/bedrock_client.py](../shared/bedrock_client.py) — LangChain ChatBedrockConverse factory
- [shared/llm_logging.py](../shared/llm_logging.py) — timed_llm_call, token extraction
- [shared/run_log.py](../shared/run_log.py) — RunLog.record_llm_call()
- [pipeline/step-02-prd-generation/pipeline/prd_generator.py](../pipeline/step-02-prd-generation/pipeline/prd_generator.py)
- [pipeline/step-04-ir-generation/pipeline/ir_pipeline/services/multi_page_service.py](../pipeline/step-04-ir-generation/pipeline/ir_pipeline/services/multi_page_service.py)
