Add updated get_context to SDKs (#231)
* feat: add optional JWT and webhook secrets to honcho instance creation * chore: ignore spurious warnings * feat: add response format if using gpt-5 model family * feat: add response models to all apis except anthropic * fix: raise NotImplementedError for response models in AsyncAnthropic client * chore: address review * [WIP] representation structure + deriver cleanup * chore: add tests, cleanup * feat: [WIP: semi-working] representation object * fix: alignment * fix: make observations hashable for dedup * fix: datetime formatting, observation counting * fix: switch to int for message id, clean up representation * feat: remove need for metadata working rep * chore: cleanup * fix: use tenacity instead of custom fns * feat: add representation and card to context if desired * feat: add semantically relevant observations * fix: pass all params to streaming, nonblocking streaming * feat: consolidate document saving, make working representation fetching much smarter * chore: add 100% test coverage of representation util * feat: basic dream infra * feat: dream queue item first pass * chore: fixes & cleanup from coderabbit * fix: dreams scheduled when new document count reaches a certain threshold * feat: wip: timed dreams (not working) * fix: test * fix: remove useless pyright ignore * fix: executing dreams * feat: dreaming * feat: [WIP] longmemeval bench * feat: add USE_PEER_CARD setting, fix longmem test driver * feat: get full working rep for dialectic in one swoop -- fix representation_from_documents to use the proper timestamp! * fix: timestamps for real, handle assistant qs in longmem * fix: remove old client, add batching to longmem * perf: remove duplicate detection, will move to background task * feat: track perf metrics on evals * feat: adjust deriver prompt to use peer_id, add question date to question, clean up deriver * fix: label metrics by task for better perf trace * chore: code review * feat: add efficiency score to longmem bench * chore: tuning and cleaning up eval * chore: bring in the big prompts * feat: add support for vllm client * feat: perf: bundle db calls in deriver and dialectic, increase max conns in docker db * feat: [WIP] realtime context object note: must download custom stainless API for SDK * feat: add merge-sessions flag to longmemeval, add SUMMARY_ENABLED flag * fix: COLLECT_METRICS default false * chore: display start/end message ids, don't include in metrics * fix: break large messages apart for eval * fix: only get/create collection when needed * feat: properly attribute documents with message id ranges and add session name column to documents * fix: revert move of get_or_create_collection (need for fkey) * fix: always get collection with peer name even if it's none * chore: coderabbit * fix: bug in get context feat: get context updates in ts sdk * feat: viz * chore: update honcho-ai/core, remove WIPs * fix: consistent ordering, comment nits, removed excess dreamer init * fix: test int->str * fix: Add validation and update async python client * fix: add validation for last_user_message as well * fix: add deeper validation to getContext in typescript sdk * fix: let session context take a Message object for lastUserMessage to match python sdk behavior * fix: use PeerIdSchema * fix: allow peer object as argument * fix: lastUserMessage min length 1 --------- Co-authored-by: Vineeth Voruganti <13438633+VVoruganti@users.noreply.github.com>
This commit is contained in:
parent
f38230fd92
commit
a3d98afdfe
|
|
@ -8,7 +8,7 @@ authors = [
|
|||
{ name = "Plastic Labs", email = "hello@plasticlabs.ai" },
|
||||
]
|
||||
dependencies = [
|
||||
"honcho-core>=1.4.1-alpha.0",
|
||||
"honcho-core>=1.5.0",
|
||||
"httpx>=0.28.0, <1",
|
||||
"pydantic>=2.0.0, <3",
|
||||
"typing-extensions>=4.12.0; python_version < \"3.12\"",
|
||||
|
|
|
|||
|
|
@ -427,11 +427,23 @@ class AsyncSession(BaseModel):
|
|||
tokens: int | None = Field(
|
||||
None, gt=0, description="Maximum number of tokens to include in the context"
|
||||
),
|
||||
peer_target: str | None = Field(
|
||||
None,
|
||||
description="A peer ID to get context for. If given *without* `peer_perspective`, a representation and peer card will be included from the omniscient Honcho-level view of `peer_target`. If given *with* `peer_perspective`, will get the representation and card for `peer_target` *from the perspective of `peer_perspective`*.",
|
||||
),
|
||||
last_user_message: str | Message | None = Field(
|
||||
None,
|
||||
description="The most recent message (string or Message object), used to fetch semantically relevant observations and returned as part of the context object. Use this alongside `peer_target` to get a more focused context -- does nothing if `peer_target` is not provided.",
|
||||
),
|
||||
peer_perspective: str | None = Field(
|
||||
None,
|
||||
description="A peer ID to get context *from the perspective of*. If given, response will attempt to include representation and card from the perspective of `peer_perspective`. Must be provided with `peer_target`.",
|
||||
),
|
||||
) -> SessionContext:
|
||||
"""
|
||||
Get optimized context for this session within a token limit.
|
||||
|
||||
Makes an async API call to retrieve a curated list of messages that provides
|
||||
Makes an API call to retrieve a curated list of messages that provides
|
||||
optimal context for the conversation while staying within the specified
|
||||
token limit. Uses tiktoken for token counting, so results should be
|
||||
compatible with OpenAI models.
|
||||
|
|
@ -440,6 +452,9 @@ class AsyncSession(BaseModel):
|
|||
summary: Whether to include summary information
|
||||
tokens: Maximum number of tokens to include in the context. Will default
|
||||
to Honcho server configuration if not provided.
|
||||
peer_target: A peer ID to get context for. If given *without* `peer_perspective`, a representation and peer card will be included from the omniscient Honcho-level view of `peer_target`. If given *with* `peer_perspective`, will get the representation and card for `peer_target` *from the perspective of `peer_perspective`*.
|
||||
last_user_message: The most recent message (string or Message object), used to fetch semantically relevant observations and returned as part of the context object. Use this alongside `peer_target` to get a more focused context -- does nothing if `peer_target` is not provided.
|
||||
peer_perspective: A peer ID to get context *from the perspective of*. If given, response will attempt to include representation and card from the perspective of `peer_perspective`. Must be provided with `peer_target`.
|
||||
|
||||
Returns:
|
||||
A SessionContext object containing the optimized message history and
|
||||
|
|
@ -450,11 +465,32 @@ class AsyncSession(BaseModel):
|
|||
Token counting is performed using tiktoken. For models using different
|
||||
tokenizers, you may need to adjust the token limit accordingly.
|
||||
"""
|
||||
|
||||
if peer_target is None and peer_perspective is not None:
|
||||
raise ValueError(
|
||||
"You must provide a `peer_target` when `peer_perspective` is provided"
|
||||
)
|
||||
|
||||
if peer_target is None and last_user_message is not None:
|
||||
raise ValueError(
|
||||
"You must provide a `peer_target` when `last_user_message` is provided"
|
||||
)
|
||||
|
||||
last_user_message_id = (
|
||||
last_user_message.id
|
||||
if isinstance(last_user_message, Message)
|
||||
else last_user_message
|
||||
)
|
||||
context = await self._client.workspaces.sessions.get_context(
|
||||
session_id=self.id,
|
||||
workspace_id=self.workspace_id,
|
||||
tokens=tokens if tokens is not None else omit,
|
||||
summary=summary,
|
||||
last_message=last_user_message_id
|
||||
if last_user_message_id is not None
|
||||
else omit,
|
||||
peer_target=peer_target if peer_target is not None else omit,
|
||||
peer_perspective=peer_perspective if peer_perspective is not None else omit,
|
||||
)
|
||||
|
||||
# Convert the honcho_core summary to our Summary if it exists
|
||||
|
|
@ -469,7 +505,13 @@ class AsyncSession(BaseModel):
|
|||
)
|
||||
|
||||
return SessionContext(
|
||||
session_id=self.id, messages=context.messages, summary=session_summary
|
||||
session_id=self.id,
|
||||
messages=context.messages,
|
||||
summary=session_summary,
|
||||
peer_representation=str(context.peer_representation)
|
||||
if context.peer_representation
|
||||
else None,
|
||||
peer_card=context.peer_card,
|
||||
)
|
||||
|
||||
async def get_summaries(self) -> SessionSummaries:
|
||||
|
|
|
|||
|
|
@ -407,6 +407,18 @@ class Session(BaseModel):
|
|||
tokens: int | None = Field(
|
||||
None, gt=0, description="Maximum number of tokens to include in the context"
|
||||
),
|
||||
peer_target: str | None = Field(
|
||||
None,
|
||||
description="A peer ID to get context for. If given *without* `peer_perspective`, a representation and peer card will be included from the omniscient Honcho-level view of `peer_target`. If given *with* `peer_perspective`, will get the representation and card for `peer_target` *from the perspective of `peer_perspective`*.",
|
||||
),
|
||||
last_user_message: str | Message | None = Field(
|
||||
None,
|
||||
description="The most recent message (string or Message object), used to fetch semantically relevant observations and returned as part of the context object. Use this alongside `peer_target` to get a more focused context -- does nothing if `peer_target` is not provided.",
|
||||
),
|
||||
peer_perspective: str | None = Field(
|
||||
None,
|
||||
description="A peer ID to get context *from the perspective of*. If given, response will attempt to include representation and card from the perspective of `peer_perspective`. Must be provided with `peer_target`.",
|
||||
),
|
||||
) -> SessionContext:
|
||||
"""
|
||||
Get optimized context for this session within a token limit.
|
||||
|
|
@ -420,6 +432,9 @@ class Session(BaseModel):
|
|||
summary: Whether to include summary information
|
||||
tokens: Maximum number of tokens to include in the context. Will default
|
||||
to Honcho server configuration if not provided.
|
||||
peer_target: A peer ID to get context for. If given *without* `peer_perspective`, a representation and peer card will be included from the omniscient Honcho-level view of `peer_target`. If given *with* `peer_perspective`, will get the representation and card for `peer_target` *from the perspective of `peer_perspective`*.
|
||||
last_user_message: The most recent message (string or Message object), used to fetch semantically relevant observations and returned as part of the context object. Use this alongside `peer_target` to get a more focused context -- does nothing if `peer_target` is not provided.
|
||||
peer_perspective: A peer ID to get context *from the perspective of*. If given, response will attempt to include representation and card from the perspective of `peer_perspective`. Must be provided with `peer_target`.
|
||||
|
||||
Returns:
|
||||
A SessionContext object containing the optimized message history and
|
||||
|
|
@ -430,11 +445,32 @@ class Session(BaseModel):
|
|||
Token counting is performed using tiktoken. For models using different
|
||||
tokenizers, you may need to adjust the token limit accordingly.
|
||||
"""
|
||||
|
||||
if peer_target is None and peer_perspective is not None:
|
||||
raise ValueError(
|
||||
"You must provide a `peer_target` when `peer_perspective` is provided"
|
||||
)
|
||||
|
||||
if peer_target is None and last_user_message is not None:
|
||||
raise ValueError(
|
||||
"You must provide a `peer_target` when `last_user_message` is provided"
|
||||
)
|
||||
|
||||
last_user_message_id = (
|
||||
last_user_message.id
|
||||
if isinstance(last_user_message, Message)
|
||||
else last_user_message
|
||||
)
|
||||
context = self._client.workspaces.sessions.get_context(
|
||||
session_id=self.id,
|
||||
workspace_id=self.workspace_id,
|
||||
tokens=tokens if tokens is not None else omit,
|
||||
summary=summary,
|
||||
last_message=last_user_message_id
|
||||
if last_user_message_id is not None
|
||||
else omit,
|
||||
peer_target=peer_target if peer_target is not None else omit,
|
||||
peer_perspective=peer_perspective if peer_perspective is not None else omit,
|
||||
)
|
||||
|
||||
# Convert the honcho_core summary to our Summary if it exists
|
||||
|
|
@ -449,7 +485,13 @@ class Session(BaseModel):
|
|||
)
|
||||
|
||||
return SessionContext(
|
||||
session_id=self.id, messages=context.messages, summary=session_summary
|
||||
session_id=self.id,
|
||||
messages=context.messages,
|
||||
summary=session_summary,
|
||||
peer_representation=str(context.peer_representation)
|
||||
if context.peer_representation
|
||||
else None,
|
||||
peer_card=context.peer_card,
|
||||
)
|
||||
|
||||
def get_summaries(self) -> SessionSummaries:
|
||||
|
|
|
|||
|
|
@ -58,6 +58,14 @@ class SessionContext(BaseModel):
|
|||
summary: Summary | None = Field(
|
||||
None, description="Summary of the session history prior to the message cutoff"
|
||||
)
|
||||
peer_representation: str | None = Field(
|
||||
None,
|
||||
description="The peer representation, if context is requested from a specific perspective",
|
||||
)
|
||||
peer_card: list[str] | None = Field(
|
||||
None,
|
||||
description="The peer card, if context is requested from a specific perspective",
|
||||
)
|
||||
|
||||
@validate_call
|
||||
def __init__(
|
||||
|
|
@ -72,6 +80,14 @@ class SessionContext(BaseModel):
|
|||
None,
|
||||
description="Summary of the session history prior to the message cutoff",
|
||||
),
|
||||
peer_representation: str | None = Field(
|
||||
None,
|
||||
description="The peer representation, if context is requested from a specific perspective",
|
||||
),
|
||||
peer_card: list[str] | None = Field(
|
||||
None,
|
||||
description="The peer card, if context is requested from a specific perspective",
|
||||
),
|
||||
) -> None:
|
||||
"""
|
||||
Initialize a new SessionContext.
|
||||
|
|
@ -84,6 +100,8 @@ class SessionContext(BaseModel):
|
|||
session_id=session_id,
|
||||
messages=messages,
|
||||
summary=summary,
|
||||
peer_representation=peer_representation,
|
||||
peer_card=peer_card,
|
||||
)
|
||||
|
||||
def to_openai(
|
||||
|
|
@ -117,14 +135,30 @@ class SessionContext(BaseModel):
|
|||
}
|
||||
for message in self.messages
|
||||
]
|
||||
system_messages: list[dict[str, str]] = []
|
||||
|
||||
if self.peer_representation:
|
||||
peer_representation_message = {
|
||||
"role": "system",
|
||||
"content": f"<peer_representation>{self.peer_representation}</peer_representation>",
|
||||
}
|
||||
system_messages.append(peer_representation_message)
|
||||
|
||||
if self.peer_card:
|
||||
peer_card_message = {
|
||||
"role": "system",
|
||||
"content": f"<peer_card>{self.peer_card}</peer_card>",
|
||||
}
|
||||
system_messages.append(peer_card_message)
|
||||
|
||||
if self.summary:
|
||||
summary_message = {
|
||||
"role": "system",
|
||||
"content": f"<summary>{self.summary.content}</summary>",
|
||||
}
|
||||
return [summary_message, *messages]
|
||||
return messages
|
||||
system_messages.append(summary_message)
|
||||
|
||||
return system_messages + messages
|
||||
|
||||
def to_anthropic(
|
||||
self,
|
||||
|
|
@ -164,14 +198,30 @@ class SessionContext(BaseModel):
|
|||
}
|
||||
for message in self.messages
|
||||
]
|
||||
system_messages: list[dict[str, str]] = []
|
||||
|
||||
if self.peer_representation:
|
||||
peer_representation_message = {
|
||||
"role": "user",
|
||||
"content": f"<peer_representation>{self.peer_representation}</peer_representation>",
|
||||
}
|
||||
system_messages.append(peer_representation_message)
|
||||
|
||||
if self.peer_card:
|
||||
peer_card_message = {
|
||||
"role": "user",
|
||||
"content": f"<peer_card>{self.peer_card}</peer_card>",
|
||||
}
|
||||
system_messages.append(peer_card_message)
|
||||
|
||||
if self.summary:
|
||||
summary_message = {
|
||||
"role": "user",
|
||||
"content": f"<summary>{self.summary.content}</summary>",
|
||||
}
|
||||
return [summary_message, *messages]
|
||||
return messages
|
||||
system_messages.append(summary_message)
|
||||
|
||||
return system_messages + messages
|
||||
|
||||
def __len__(self) -> int:
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ function createTestMessage(id: string, content: string, peer_id: string, additio
|
|||
function createTestSummary(content: string): Summary {
|
||||
return new Summary({
|
||||
content,
|
||||
message_id: 1,
|
||||
message_id: "1",
|
||||
summary_type: 'short',
|
||||
created_at: new Date().toISOString(),
|
||||
token_count: content.length
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
"": {
|
||||
"name": "@honcho-ai/sdk",
|
||||
"dependencies": {
|
||||
"@honcho-ai/core": "^1.4.1-alpha.0",
|
||||
"@honcho-ai/core": "^1.5.0",
|
||||
"@types/node": "^24.0.1",
|
||||
"zod": "4.0.0",
|
||||
},
|
||||
|
|
@ -108,7 +108,7 @@
|
|||
|
||||
"@cspotcode/source-map-support": ["@cspotcode/source-map-support@0.8.1", "", { "dependencies": { "@jridgewell/trace-mapping": "0.3.9" } }, "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw=="],
|
||||
|
||||
"@honcho-ai/core": ["@honcho-ai/core@1.4.1-alpha.0", "", { "dependencies": { "@types/node": "^18.11.18", "@types/node-fetch": "^2.6.4", "abort-controller": "^3.0.0", "agentkeepalive": "^4.2.1", "form-data-encoder": "1.7.2", "formdata-node": "^4.3.2", "node-fetch": "^2.6.7" } }, "sha512-cRp3ug8cBe9E7NZDgCXkI0EHIgm8x5Jq/nQB0xA5629GuniS99yq6M786I//FjOZhB2YEZoOxd4Zkg412uk5Yg=="],
|
||||
"@honcho-ai/core": ["@honcho-ai/core@1.5.0", "", { "dependencies": { "@types/node": "^18.11.18", "@types/node-fetch": "^2.6.4", "abort-controller": "^3.0.0", "agentkeepalive": "^4.2.1", "form-data-encoder": "1.7.2", "formdata-node": "^4.3.2", "node-fetch": "^2.6.7" } }, "sha512-kjYhCO0S9Ll3DfVpK8MetjY1PlD9FG6QGtudlMnSwfTWGzXFjL/OafpoE3SZ3c9BVY48JIbW9IttFUZFekv/xg=="],
|
||||
|
||||
"@istanbuljs/load-nyc-config": ["@istanbuljs/load-nyc-config@1.1.0", "", { "dependencies": { "camelcase": "^5.3.1", "find-up": "^4.1.0", "get-package-type": "^0.1.0", "js-yaml": "^3.13.1", "resolve-from": "^5.0.0" } }, "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ=="],
|
||||
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@
|
|||
"test:coverage": "jest --coverage"
|
||||
},
|
||||
"dependencies": {
|
||||
"@honcho-ai/core": "^1.4.1-alpha.0",
|
||||
"@honcho-ai/core": "^1.5.0",
|
||||
"@types/node": "^24.0.1",
|
||||
"zod": "4.0.0"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -451,10 +451,21 @@ export class Session {
|
|||
* compatible with OpenAI models. The context optimization balances
|
||||
* recency and relevance to provide the best conversational context.
|
||||
*
|
||||
* @param summary - Whether to include summary information in the context.
|
||||
* When true, includes session summary if available. Defaults to true
|
||||
* @param tokens - Maximum number of tokens to include in the context. If not provided,
|
||||
* uses the server's default configuration
|
||||
* @param options - Configuration options for context retrieval
|
||||
* @param options.summary - Whether to include summary information in the context.
|
||||
* When true, includes session summary if available. Defaults to true
|
||||
* @param options.tokens - Maximum number of tokens to include in the context. If not provided,
|
||||
* uses the server's default configuration
|
||||
* @param options.peerTarget - The target of the perspective. If given without `peerPerspective`,
|
||||
* will get the Honcho-level representation and peer card for this peer.
|
||||
* If given with `peerPerspective`, will get the representation and card
|
||||
* for this peer from the perspective of that peer.
|
||||
* @param options.lastUserMessage - The most recent message, used to fetch semantically relevant
|
||||
* observations and returned as part of the context object.
|
||||
* Can be either a message ID string or a Message object.
|
||||
* @param options.peerPerspective - A peer to get context for. If given, response will attempt to
|
||||
* include representation and card from the perspective of that peer.
|
||||
* Must be provided with `peerTarget`.
|
||||
* @returns Promise resolving to a SessionContext object containing the optimized
|
||||
* message history and summary (if available) that maximizes conversational
|
||||
* context while respecting the token limit
|
||||
|
|
@ -462,25 +473,103 @@ export class Session {
|
|||
* @note Token counting is performed using tiktoken. For models using different
|
||||
* tokenizers, you may need to adjust the token limit accordingly.
|
||||
*/
|
||||
async getContext(
|
||||
summary?: boolean,
|
||||
tokens?: number,
|
||||
peerTarget?: string | Peer,
|
||||
lastUserMessage?: string | Message,
|
||||
peerPerspective?: string | Peer
|
||||
): Promise<SessionContext>
|
||||
async getContext(options?: {
|
||||
summary?: boolean
|
||||
tokens?: number
|
||||
}): Promise<SessionContext> {
|
||||
peerTarget?: string | Peer
|
||||
lastUserMessage?: string | Message
|
||||
peerPerspective?: string | Peer
|
||||
}): Promise<SessionContext>
|
||||
async getContext(
|
||||
summaryOrOptions?:
|
||||
| boolean
|
||||
| {
|
||||
summary?: boolean
|
||||
tokens?: number
|
||||
peerTarget?: string | Peer
|
||||
lastUserMessage?: string | Message
|
||||
peerPerspective?: string | Peer
|
||||
},
|
||||
tokens?: number,
|
||||
peerTarget?: string | Peer,
|
||||
lastUserMessage?: string | Message,
|
||||
peerPerspective?: string | Peer
|
||||
): Promise<SessionContext> {
|
||||
// Normalize positional arguments into options object
|
||||
let options: {
|
||||
summary?: boolean
|
||||
tokens?: number
|
||||
peerTarget?: string
|
||||
lastUserMessage?: string
|
||||
peerPerspective?: string
|
||||
}
|
||||
|
||||
if (
|
||||
typeof summaryOrOptions === 'boolean' ||
|
||||
(summaryOrOptions === undefined && arguments.length > 1)
|
||||
) {
|
||||
// Positional arguments pattern
|
||||
options = {
|
||||
summary: summaryOrOptions as boolean | undefined,
|
||||
tokens,
|
||||
peerTarget: typeof peerTarget === 'object' ? peerTarget.id : peerTarget,
|
||||
lastUserMessage:
|
||||
typeof lastUserMessage === 'string'
|
||||
? lastUserMessage
|
||||
: lastUserMessage?.id,
|
||||
peerPerspective:
|
||||
typeof peerPerspective === 'object'
|
||||
? peerPerspective.id
|
||||
: peerPerspective,
|
||||
}
|
||||
} else {
|
||||
// Options object pattern
|
||||
options = (summaryOrOptions as typeof options) || {}
|
||||
}
|
||||
|
||||
const contextParams = ContextParamsSchema.parse({
|
||||
summary: options?.summary,
|
||||
tokens: options?.tokens,
|
||||
summary: options.summary,
|
||||
tokens: options.tokens,
|
||||
peerTarget: options.peerTarget,
|
||||
lastUserMessage: options.lastUserMessage,
|
||||
peerPerspective: options.peerPerspective,
|
||||
})
|
||||
|
||||
// Extract message ID if lastUserMessage is a Message object
|
||||
const lastMessageId =
|
||||
typeof contextParams.lastUserMessage === 'string'
|
||||
? contextParams.lastUserMessage
|
||||
: contextParams.lastUserMessage?.id
|
||||
|
||||
const context = await this._client.workspaces.sessions.getContext(
|
||||
this.workspaceId,
|
||||
this.id,
|
||||
{
|
||||
tokens: contextParams.tokens,
|
||||
summary: contextParams.summary,
|
||||
last_message: lastMessageId,
|
||||
peer_target: contextParams.peerTarget,
|
||||
peer_perspective: contextParams.peerPerspective,
|
||||
}
|
||||
)
|
||||
// Convert the summary response to Summary object if present
|
||||
const summary = context.summary ? new Summary(context.summary) : null
|
||||
return new SessionContext(this.id, context.messages, summary)
|
||||
return new SessionContext(
|
||||
this.id,
|
||||
context.messages,
|
||||
summary,
|
||||
context.peer_representation
|
||||
? JSON.stringify(context.peer_representation)
|
||||
: null,
|
||||
context.peer_card ?? null
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -102,21 +102,37 @@ export class SessionContext {
|
|||
*/
|
||||
readonly summary: Summary | null
|
||||
|
||||
/**
|
||||
* The peer representation, if context is requested from a specific perspective.
|
||||
*/
|
||||
readonly peerRepresentation: string | null
|
||||
|
||||
/**
|
||||
* The peer card, if context is requested from a specific perspective.
|
||||
*/
|
||||
readonly peerCard: string[] | null
|
||||
|
||||
/**
|
||||
* Initialize a new SessionContext.
|
||||
*
|
||||
* @param sessionId ID of the session this context belongs to
|
||||
* @param messages List of Message objects to include in the context
|
||||
* @param summary Summary of the session history prior to the message cutoff
|
||||
* @param peerRepresentation The peer representation, if context is requested from a specific perspective
|
||||
* @param peerCard The peer card, if context is requested from a specific perspective
|
||||
*/
|
||||
constructor(
|
||||
sessionId: string,
|
||||
messages: Message[],
|
||||
summary: Summary | null = null
|
||||
summary: Summary | null = null,
|
||||
peerRepresentation: string | null = null,
|
||||
peerCard: string[] | null = null
|
||||
) {
|
||||
this.sessionId = sessionId
|
||||
this.messages = messages
|
||||
this.summary = summary
|
||||
this.peerRepresentation = peerRepresentation
|
||||
this.peerCard = peerCard
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -136,18 +152,36 @@ export class SessionContext {
|
|||
assistant: string | Peer
|
||||
): Array<{ role: string; content: string; name?: string }> {
|
||||
const assistantId = typeof assistant === 'string' ? assistant : assistant.id
|
||||
const summaryMessage = this.summary
|
||||
? {
|
||||
role: 'system',
|
||||
content: `<summary>${this.summary.content}</summary>`,
|
||||
}
|
||||
: null
|
||||
const messages = this.messages.map((message) => ({
|
||||
role: message.peer_id === assistantId ? 'assistant' : 'user',
|
||||
name: message.peer_id,
|
||||
content: message.content,
|
||||
}))
|
||||
return summaryMessage ? [summaryMessage, ...messages] : messages
|
||||
|
||||
const systemMessages: Array<{ role: string; content: string }> = []
|
||||
|
||||
if (this.peerRepresentation) {
|
||||
systemMessages.push({
|
||||
role: 'system',
|
||||
content: `<peer_representation>${this.peerRepresentation}</peer_representation>`,
|
||||
})
|
||||
}
|
||||
|
||||
if (this.peerCard) {
|
||||
systemMessages.push({
|
||||
role: 'system',
|
||||
content: `<peer_card>${this.peerCard}</peer_card>`,
|
||||
})
|
||||
}
|
||||
|
||||
if (this.summary) {
|
||||
systemMessages.push({
|
||||
role: 'system',
|
||||
content: `<summary>${this.summary.content}</summary>`,
|
||||
})
|
||||
}
|
||||
|
||||
return [...systemMessages, ...messages]
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -170,12 +204,6 @@ export class SessionContext {
|
|||
assistant: string | Peer
|
||||
): Array<{ role: string; content: string }> {
|
||||
const assistantId = typeof assistant === 'string' ? assistant : assistant.id
|
||||
const summaryMessage = this.summary
|
||||
? {
|
||||
role: 'user',
|
||||
content: `<summary>${this.summary.content}</summary>`,
|
||||
}
|
||||
: null
|
||||
const messages = this.messages.map((message) =>
|
||||
message.peer_id === assistantId
|
||||
? {
|
||||
|
|
@ -187,7 +215,31 @@ export class SessionContext {
|
|||
content: `${message.peer_id}: ${message.content}`,
|
||||
}
|
||||
)
|
||||
return summaryMessage ? [summaryMessage, ...messages] : messages
|
||||
|
||||
const systemMessages: Array<{ role: string; content: string }> = []
|
||||
|
||||
if (this.peerRepresentation) {
|
||||
systemMessages.push({
|
||||
role: 'user',
|
||||
content: `<peer_representation>${this.peerRepresentation}</peer_representation>`,
|
||||
})
|
||||
}
|
||||
|
||||
if (this.peerCard) {
|
||||
systemMessages.push({
|
||||
role: 'user',
|
||||
content: `<peer_card>${this.peerCard}</peer_card>`,
|
||||
})
|
||||
}
|
||||
|
||||
if (this.summary) {
|
||||
systemMessages.push({
|
||||
role: 'user',
|
||||
content: `<summary>${this.summary.content}</summary>`,
|
||||
})
|
||||
}
|
||||
|
||||
return [...systemMessages, ...messages]
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import type { Message } from '@honcho-ai/core/resources/workspaces/sessions/messages'
|
||||
import { z } from 'zod'
|
||||
|
||||
/**
|
||||
|
|
@ -123,16 +124,56 @@ export const ChatQuerySchema = z.object({
|
|||
sessionId: z.string().optional(),
|
||||
})
|
||||
|
||||
/**
|
||||
* Schema for validating Message objects from the core SDK.
|
||||
*/
|
||||
const MessageSchema: z.ZodType<Message> = z.object({
|
||||
id: z.string(),
|
||||
content: z.string(),
|
||||
created_at: z.string(),
|
||||
peer_id: z.string(),
|
||||
session_id: z.string(),
|
||||
token_count: z.number(),
|
||||
workspace_id: z.string(),
|
||||
metadata: z.record(z.string(), z.unknown()).optional(),
|
||||
}) as z.ZodType<Message>
|
||||
|
||||
/**
|
||||
* Schema for context retrieval parameters.
|
||||
*/
|
||||
export const ContextParamsSchema = z.object({
|
||||
summary: z.boolean().optional(),
|
||||
tokens: z
|
||||
.number()
|
||||
.positive('Token limit must be a positive number')
|
||||
.optional(),
|
||||
})
|
||||
export const ContextParamsSchema = z
|
||||
.object({
|
||||
summary: z.boolean().optional(),
|
||||
tokens: z
|
||||
.number()
|
||||
.positive('Token limit must be a positive number')
|
||||
.optional(),
|
||||
lastUserMessage: z
|
||||
.union([
|
||||
z.string().min(1, 'Last user message must be a non-empty string'),
|
||||
MessageSchema,
|
||||
])
|
||||
.optional(),
|
||||
peerTarget: PeerIdSchema.optional(),
|
||||
peerPerspective: PeerIdSchema.optional(),
|
||||
})
|
||||
.superRefine((data, ctx) => {
|
||||
if (data.lastUserMessage && !data.peerTarget) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: 'peerTarget is required when lastUserMessage is provided',
|
||||
path: ['lastUserMessage'],
|
||||
})
|
||||
}
|
||||
|
||||
if (data.peerPerspective && !data.peerTarget) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: 'peerTarget is required when peerPerspective is provided',
|
||||
path: ['peerPerspective'],
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* Schema for deriver status options.
|
||||
|
|
|
|||
|
|
@ -436,11 +436,8 @@ try:
|
|||
print_settings(value, full_key, max_depth, current_depth + 1)
|
||||
else:
|
||||
# Mask sensitive information
|
||||
if isinstance(value, str) and any(sensitive in value.lower() for sensitive in ['password', 'secret', 'key', 'token']):
|
||||
if 'testpwd' in value:
|
||||
masked_value = value.replace('testpwd', '***')
|
||||
else:
|
||||
masked_value = '***'
|
||||
if isinstance(full_key, str) and any(sensitive in full_key.lower() for sensitive in ['password', 'secret', 'key', 'uri']):
|
||||
masked_value = '*' * len(value) if value else 'None'
|
||||
else:
|
||||
masked_value = value
|
||||
print(f" {{key}}: {{masked_value}}")
|
||||
|
|
|
|||
10
uv.lock
10
uv.lock
|
|
@ -1,5 +1,5 @@
|
|||
version = 1
|
||||
revision = 3
|
||||
revision = 2
|
||||
requires-python = ">=3.10"
|
||||
resolution-markers = [
|
||||
"python_full_version >= '3.13'",
|
||||
|
|
@ -780,7 +780,7 @@ dev = [
|
|||
|
||||
[package.metadata]
|
||||
requires-dist = [
|
||||
{ name = "honcho-core", specifier = ">=1.4.1a0" },
|
||||
{ name = "honcho-core", specifier = ">=1.5.0" },
|
||||
{ name = "httpx", specifier = ">=0.28.0,<1" },
|
||||
{ name = "pydantic", specifier = ">=2.0.0,<3" },
|
||||
{ name = "typing-extensions", marker = "python_full_version < '3.12'", specifier = ">=4.12.0" },
|
||||
|
|
@ -791,7 +791,7 @@ dev = [{ name = "ruff", specifier = ">=0.11.13" }]
|
|||
|
||||
[[package]]
|
||||
name = "honcho-core"
|
||||
version = "1.4.1a0"
|
||||
version = "1.5.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "anyio" },
|
||||
|
|
@ -801,9 +801,9 @@ dependencies = [
|
|||
{ name = "sniffio" },
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/2d/2f/fbf3ca017ffcdaef92e26fe84b77268c1c0b26f95e282d8684f50932d81a/honcho_core-1.4.1a0.tar.gz", hash = "sha256:e0023f46c65500d727e3fb1275781ae1e0b162c226a01352afd45c421c5f350b", size = 130826, upload-time = "2025-10-01T03:23:27.143Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/75/cd/05d2a21afd037673637e390de411cd66ca56b07e524cd7ead65c655a2b49/honcho_core-1.5.0.tar.gz", hash = "sha256:4876195dad16db437117d40a1d5e34ff88974e8eca6d093f3653b4ac2bda3c6d", size = 132236, upload-time = "2025-10-08T18:30:15.777Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/83/5f/2aa746b7fd92690ef782dcacc56a5c47a19a999b9729597c04c7cc509e52/honcho_core-1.4.1a0-py3-none-any.whl", hash = "sha256:8b4c86e3ac3964ff56fe6a449b9fa23be49530609c34bdf35c9ecde7cd871bba", size = 121704, upload-time = "2025-10-01T03:23:25.66Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ef/4e/9ef193bba00c521be1152791d2ba37a98bdb58ece8f8ce7863b710a4f511/honcho_core-1.5.0-py3-none-any.whl", hash = "sha256:01db345371d6e80230b202c797a73f9f086dc244936522dbc39efb05f4cee306", size = 123210, upload-time = "2025-10-08T18:30:14.277Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
|
|||
Loading…
Reference in New Issue