feat(conversation): integrate ClaudeAgentServiceV2 for consulting workflow

- Switch ConversationService to use ClaudeAgentServiceV2
- Pass consultingState and deviceInfo from conversation to context
- Handle state_update chunks and save updated state to database
- Move dotenv to dependencies for migration runtime

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
hailin 2026-01-24 06:43:19 -08:00
parent c0a9710943
commit 9f2bdee8d9
2 changed files with 25 additions and 5 deletions

View File

@ -29,6 +29,7 @@
"@nestjs/websockets": "^10.0.0", "@nestjs/websockets": "^10.0.0",
"class-transformer": "^0.5.1", "class-transformer": "^0.5.1",
"class-validator": "^0.14.0", "class-validator": "^0.14.0",
"dotenv": "^16.3.0",
"ioredis": "^5.3.0", "ioredis": "^5.3.0",
"kafkajs": "^2.2.4", "kafkajs": "^2.2.4",
"pg": "^8.11.0", "pg": "^8.11.0",
@ -45,7 +46,6 @@
"@types/node": "^20.10.0", "@types/node": "^20.10.0",
"@types/socket.io": "^3.0.2", "@types/socket.io": "^3.0.2",
"@types/uuid": "^9.0.0", "@types/uuid": "^9.0.0",
"dotenv": "^16.3.0",
"jest": "^29.7.0", "jest": "^29.7.0",
"ts-jest": "^29.1.0", "ts-jest": "^29.1.0",
"ts-node": "^10.9.0", "ts-node": "^10.9.0",

View File

@ -11,10 +11,10 @@ import {
MessageType, MessageType,
} from '../domain/entities/message.entity'; } from '../domain/entities/message.entity';
import { import {
ClaudeAgentService, ClaudeAgentServiceV2,
ConversationContext, ConversationContext,
StreamChunk, StreamChunk,
} from '../infrastructure/claude/claude-agent.service'; } from '../infrastructure/claude/claude-agent-v2.service';
export interface CreateConversationDto { export interface CreateConversationDto {
userId: string; userId: string;
@ -45,7 +45,7 @@ export class ConversationService {
private conversationRepo: Repository<ConversationEntity>, private conversationRepo: Repository<ConversationEntity>,
@InjectRepository(MessageEntity) @InjectRepository(MessageEntity)
private messageRepo: Repository<MessageEntity>, private messageRepo: Repository<MessageEntity>,
private claudeAgentService: ClaudeAgentService, private claudeAgentService: ClaudeAgentServiceV2,
) {} ) {}
/** /**
@ -134,7 +134,7 @@ export class ConversationService {
take: 20, // Last 20 messages for context take: 20, // Last 20 messages for context
}); });
// Build context with support for multimodal messages // Build context with support for multimodal messages and consulting state (V2)
const context: ConversationContext = { const context: ConversationContext = {
userId: dto.userId, userId: dto.userId,
conversationId: dto.conversationId, conversationId: dto.conversationId,
@ -149,11 +149,15 @@ export class ConversationService {
} }
return msg; return msg;
}), }),
// V2: Pass consulting state from conversation (cast through unknown for JSON/Date compatibility)
consultingState: conversation.consultingState as unknown as ConversationContext['consultingState'],
deviceInfo: conversation.deviceInfo,
}; };
// Collect full response for saving // Collect full response for saving
let fullResponse = ''; let fullResponse = '';
const toolCalls: Array<{ name: string; input: unknown; result: unknown }> = []; const toolCalls: Array<{ name: string; input: unknown; result: unknown }> = [];
let updatedState: ConversationContext['consultingState'] | undefined;
// Stream response from Claude (with attachments for multimodal support) // Stream response from Claude (with attachments for multimodal support)
for await (const chunk of this.claudeAgentService.sendMessage( for await (const chunk of this.claudeAgentService.sendMessage(
@ -174,11 +178,27 @@ export class ConversationService {
if (lastToolCall) { if (lastToolCall) {
lastToolCall.result = chunk.toolResult; lastToolCall.result = chunk.toolResult;
} }
} else if (chunk.type === 'state_update' && chunk.newState) {
// V2: Capture updated consulting state
updatedState = chunk.newState;
} }
yield chunk; yield chunk;
} }
// V2: Save updated consulting state to conversation
if (updatedState) {
// Convert state to JSON-compatible format for database storage
const stateForDb = JSON.parse(JSON.stringify(updatedState));
await this.conversationRepo.update(conversation.id, {
consultingState: stateForDb,
consultingStage: updatedState.currentStageId,
collectedInfo: stateForDb.collectedInfo,
recommendedPrograms: updatedState.assessmentResult?.recommendedPrograms,
conversionPath: updatedState.conversionPath,
});
}
// Save assistant response // Save assistant response
const assistantMessage = this.messageRepo.create({ const assistantMessage = this.messageRepo.create({
conversationId: dto.conversationId, conversationId: dto.conversationId,