Compare commits
1 Commits
c8fc662552
...
v2.4.14
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
949bf6c86f |
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "app-ts",
|
"name": "app-ts",
|
||||||
"version": "2.4.13",
|
"version": "2.4.14",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "app-ts",
|
"name": "app-ts",
|
||||||
"version": "2.4.13",
|
"version": "2.4.14",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@anthropic-ai/sdk": "^0.102.0",
|
"@anthropic-ai/sdk": "^0.102.0",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "app-ts",
|
"name": "app-ts",
|
||||||
"version": "2.4.13",
|
"version": "2.4.14",
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "dist/server.js",
|
"main": "dist/server.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -534,6 +534,27 @@ describe("agenticChat loop (SDK mocked)", () => {
|
|||||||
expect(usageRows.length).toBe(2);
|
expect(usageRows.length).toBe(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("injects the caller's identity into the system prompt", async () => {
|
||||||
|
createMock.mockReset();
|
||||||
|
createMock.mockResolvedValueOnce({
|
||||||
|
stop_reason: "end_turn",
|
||||||
|
content: [{ type: "text", text: "Ahoj." }],
|
||||||
|
usage: { input_tokens: 10, output_tokens: 5 },
|
||||||
|
});
|
||||||
|
await agenticChat([{ role: "user", content: "Kdo jsem?" }], {
|
||||||
|
userId: fixUserId,
|
||||||
|
roleName: "viewer",
|
||||||
|
permissions: ["attendance.record"],
|
||||||
|
});
|
||||||
|
const system: string = createMock.mock.calls[0][0].system;
|
||||||
|
expect(system).toContain(`${FIX.first} ${FIX.last}`);
|
||||||
|
expect(system).toContain(`user_id ${fixUserId}`);
|
||||||
|
// Cleanup the usage row this extra fixture-user turn recorded.
|
||||||
|
await prisma.ai_usage.deleteMany({
|
||||||
|
where: { user_id: fixUserId, kind: "agent" },
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it("a user without permissions gets NO tools passed to the model", async () => {
|
it("a user without permissions gets NO tools passed to the model", async () => {
|
||||||
createMock.mockReset();
|
createMock.mockReset();
|
||||||
createMock.mockResolvedValueOnce({
|
createMock.mockResolvedValueOnce({
|
||||||
|
|||||||
@@ -257,12 +257,25 @@ export interface ToolTraceEntry {
|
|||||||
// Phase 2a (read-only agent). The date is interpolated so "tento měsíc"
|
// Phase 2a (read-only agent). The date is interpolated so "tento měsíc"
|
||||||
// questions resolve correctly — it changes once a day, which is fine because
|
// questions resolve correctly — it changes once a day, which is fine because
|
||||||
// this prompt is small and we don't use prompt caching here.
|
// this prompt is small and we don't use prompt caching here.
|
||||||
function agentSystemPrompt(tools: Anthropic.Tool[]): string {
|
interface CallerIdentity {
|
||||||
|
name: string;
|
||||||
|
username: string;
|
||||||
|
userId: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
function agentSystemPrompt(
|
||||||
|
tools: Anthropic.Tool[],
|
||||||
|
caller: CallerIdentity | null,
|
||||||
|
): string {
|
||||||
const today = new Date();
|
const today = new Date();
|
||||||
const dateStr = `${today.getFullYear()}-${String(today.getMonth() + 1).padStart(2, "0")}-${String(today.getDate()).padStart(2, "0")}`;
|
const dateStr = `${today.getFullYear()}-${String(today.getMonth() + 1).padStart(2, "0")}-${String(today.getDate()).padStart(2, "0")}`;
|
||||||
const hasFindEmployee = tools.some((t) => t.name === "find_employee");
|
const hasFindEmployee = tools.some((t) => t.name === "find_employee");
|
||||||
return (
|
return (
|
||||||
"Jsi Odin, asistent v interním systému české firmy (docházka, fakturace, nabídky, objednávky, projekty, sklad). " +
|
"Jsi Odin, asistent v interním systému české firmy (docházka, fakturace, nabídky, objednávky, projekty, sklad). " +
|
||||||
|
(caller
|
||||||
|
? `Přihlášený uživatel: ${caller.name} (user_id ${caller.userId}, username ${caller.username}). ` +
|
||||||
|
"Otázky v první osobě („moje docházka“, „kolik jsem najel“, „můj plán práce“) se týkají tohoto uživatele — použij jeho user_id a na identitu se nikdy neptej. "
|
||||||
|
: "") +
|
||||||
"Odpovídej VŽDY česky, stručně a věcně; částky formátuj s měnou. " +
|
"Odpovídej VŽDY česky, stručně a věcně; částky formátuj s měnou. " +
|
||||||
"Odpovědi piš jako PROSTÝ TEXT — žádný Markdown (žádné tabulky, **tučné**, nadpisy); výčty piš jako řádky s pomlčkou. " +
|
"Odpovědi piš jako PROSTÝ TEXT — žádný Markdown (žádné tabulky, **tučné**, nadpisy); výčty piš jako řádky s pomlčkou. " +
|
||||||
(tools.length > 0
|
(tools.length > 0
|
||||||
@@ -293,6 +306,19 @@ export async function agenticChat(
|
|||||||
ctx: AiAuthCtx,
|
ctx: AiAuthCtx,
|
||||||
): Promise<{ reply: string; toolTrace: ToolTraceEntry[] }> {
|
): Promise<{ reply: string; toolTrace: ToolTraceEntry[] }> {
|
||||||
const tools = toolDefinitionsFor(ctx);
|
const tools = toolDefinitionsFor(ctx);
|
||||||
|
// The model must know who it is talking to — first-person questions
|
||||||
|
// ("moje docházka") are unanswerable otherwise. PK lookup, negligible cost.
|
||||||
|
const me = await prisma.users.findUnique({
|
||||||
|
where: { id: ctx.userId },
|
||||||
|
select: { first_name: true, last_name: true, username: true },
|
||||||
|
});
|
||||||
|
const caller = me
|
||||||
|
? {
|
||||||
|
name: `${me.first_name} ${me.last_name}`.trim() || me.username,
|
||||||
|
username: me.username,
|
||||||
|
userId: ctx.userId,
|
||||||
|
}
|
||||||
|
: null;
|
||||||
const convo: Anthropic.MessageParam[] = messages.map((m) => ({
|
const convo: Anthropic.MessageParam[] = messages.map((m) => ({
|
||||||
role: m.role,
|
role: m.role,
|
||||||
content: m.content,
|
content: m.content,
|
||||||
@@ -305,7 +331,7 @@ export async function agenticChat(
|
|||||||
res = await client().messages.create({
|
res = await client().messages.create({
|
||||||
model: AI_MODEL,
|
model: AI_MODEL,
|
||||||
max_tokens: 2048,
|
max_tokens: 2048,
|
||||||
system: agentSystemPrompt(tools),
|
system: agentSystemPrompt(tools, caller),
|
||||||
tools: tools.length > 0 ? tools : undefined,
|
tools: tools.length > 0 ? tools : undefined,
|
||||||
messages: convo,
|
messages: convo,
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user